Full Offline Math AI Calculator by Leon Luk

Fully working draggable, scrollable calculator with offline math AI chat, resets on error, persistent state, Ctrl+L reset, pink-purple theme

// ==UserScript==
// @name         Full Offline Math AI Calculator by Leon Luk
// @namespace    http://tampermonkey.net/
// @version      69.69
// @description  Fully working draggable, scrollable calculator with offline math AI chat, resets on error, persistent state, Ctrl+L reset, pink-purple theme
// @author       Leon Luk
// @match        *://*/*
// @grant        none
// @license      Proprietary
// ==/UserScript==

(function(){
    'use strict';

    const DEFAULT_WIDTH = 350;
    const DEFAULT_HEIGHT = 600;
    const DEFAULT_LEFT = 70;
    const DEFAULT_TOP = window.innerHeight/2 - DEFAULT_HEIGHT/2;
    const STORAGE_POS = 'leonluk_calc_pos';
    const STORAGE_VIS = 'leonluk_calc_visible';

    function createCalculator(){
        const calc = document.createElement('div');
        Object.assign(calc.style,{position:'fixed', top: DEFAULT_TOP+'px', left: DEFAULT_LEFT+'px', width: DEFAULT_WIDTH+'px', height: DEFAULT_HEIGHT+'px', padding:'15px', background:'linear-gradient(135deg,#ec4899,#8b5cf6)', color:'white', borderRadius:'10px', boxShadow:'4px 0 15px rgba(0,0,0,0.6)', zIndex:'99999999', fontFamily:'monospace', display:'flex', flexDirection:'column', overflow:'auto'});

        const savedPos = JSON.parse(localStorage.getItem(STORAGE_POS));
        if(savedPos){
            calc.style.left = savedPos.left+'px';
            calc.style.top = savedPos.top+'px';
            calc.style.width = savedPos.width+'px';
            calc.style.height = savedPos.height+'px';
        }

        const savedVis = localStorage.getItem(STORAGE_VIS);
        if(savedVis==='hidden') calc.style.display='none';

        calc.innerHTML = `
        <div id="calc-header" style="display:flex;justify-content:space-between;align-items:center;font-weight:bold;margin-bottom:5px;cursor:move;font-size:16px;">
            <span>Calculator</span>
            <div>
                <button id="refresh-btn" style="background:none;border:none;color:white;font-size:18px;cursor:pointer;line-height:1;padding:0;margin-right:5px;">⟳</button>
                <button id="minimize-btn" style="background:none;border:none;color:white;font-size:20px;cursor:pointer;line-height:1;padding:0;">—</button>
            </div>
        </div>
        <input type="text" id="calc-display" value="0" readonly style="width:100%;padding:10px;font-size:18px;border:none;border-radius:5px;text-align:right;background:rgba(0,0,0,0.3);color:white;margin-bottom:5px;flex-shrink:0;">
        <div id="calc-buttons" style="display:grid;grid-template-columns:repeat(6,1fr);gap:5px;flex-shrink:0;"></div>
        <div id="chat-container" style="margin-top:10px;flex-grow:1;display:flex;flex-direction:column;">
            <div id="chat-messages" style="flex-grow:1;overflow-y:auto;scroll-behavior:smooth;background:rgba(0,0,0,0.2);padding:5px;border-radius:5px;margin-bottom:5px;font-size:12px;display:flex;flex-direction:column;gap:3px;"></div>
            <input id="chat-input" type="text" placeholder="Ask math questions..." style="width:100%;padding:5px;font-size:12px;border:none;border-radius:3px;background:rgba(255,255,255,0.15);color:white;flex-shrink:0;">
        </div>
        <div style="text-align:right;font-size:10px;opacity:0.8;">by Leon Luk</div>
        `;

        document.body.appendChild(calc);

        const display = calc.querySelector('#calc-display');
        const buttonsContainer = calc.querySelector('#calc-buttons');
        const minimizeBtn = calc.querySelector('#minimize-btn');
        const refreshBtn = calc.querySelector('#refresh-btn');
        const chatMessages = calc.querySelector('#chat-messages');
        const chatInput = calc.querySelector('#chat-input');

        const buttons = ['7','8','9','/','C','(', '4','5','6','*','^',')', '1','2','3','-','√','x²','0','.','=','+','%','+/-','sin','cos','tan','asin','acos','atan','log','ln','pi','e'];

        buttons.forEach(key=>{
            const btn = document.createElement('button');
            btn.innerText = key;
            Object.assign(btn.style,{padding:'10px',fontSize:'14px',border:'none',borderRadius:'5px',background:'rgba(255,255,255,0.15)',color:'white',cursor:'pointer',transition:'background 0.2s ease, transform 0.1s ease'});
            btn.onmouseenter = ()=>btn.style.background='rgba(255,255,255,0.25)';
            btn.onmouseleave = ()=>btn.style.background='rgba(255,255,255,0.15)';
            btn.onmousedown = ()=>btn.style.transform='scale(0.95)';
            btn.onmouseup = ()=>btn.style.transform='scale(1)';
            btn.addEventListener('click',()=>{
                try{
                    if(key==='C'){display.value='0';}
                    else if(key==='='){ 
                        let expr = display.value.replace(/\^/g,'**').replace(/√/g,'Math.sqrt')
                            .replace(/pi/g,'Math.PI').replace(/e/g,'Math.E')
                            .replace(/sin/g,'Math.sin').replace(/cos/g,'Math.cos').replace(/tan/g,'Math.tan')
                            .replace(/asin/g,'Math.asin').replace(/acos/g,'Math.acos').replace(/atan/g,'Math.atan')
                            .replace(/log/g,'Math.log10').replace(/ln/g,'Math.log');
                        display.value = eval(expr);
                    }
                    else if(key==='x²'){display.value+='**2';}
                    else if(key==='+/-'){display.value=display.value.startsWith('-')?display.value.slice(1):'-'+display.value;}
                    else{display.value += key;}
                }catch{
                    display.value='0';
                }
            });
            buttonsContainer.appendChild(btn);
        });

        function solveExpression(input){
            try{
                let expr = input.replace(/\^/g,'**').replace(/√/g,'Math.sqrt')
                    .replace(/pi/g,'Math.PI').replace(/e/g,'Math.E')
                    .replace(/sin/g,'Math.sin').replace(/cos/g,'Math.cos').replace(/tan/g,'Math.tan')
                    .replace(/asin/g,'Math.asin').replace(/acos/g,'Math.acos').replace(/atan/g,'Math.atan')
                    .replace(/log/g,'Math.log10').replace(/ln/g,'Math.log');
                let result = eval(expr);
                return `${input} = ${result}`;
            } catch(e){
                return `Sorry, could not compute: ${input}`;
            }
        }

        chatInput.addEventListener('keydown', e=>{
            if(e.key==='Enter' && chatInput.value.trim()!==''){
                const userMsg = chatInput.value.trim();
                const msgDiv = document.createElement('div');
                msgDiv.innerText='You: '+userMsg;
                msgDiv.style.alignSelf='flex-end'; msgDiv.style.background='rgba(255,255,255,0.2)'; msgDiv.style.padding='3px 5px'; msgDiv.style.borderRadius='4px';
                chatMessages.appendChild(msgDiv);

                chatInput.value='';

                const botDiv = document.createElement('div'); botDiv.innerText='AI: ...'; botDiv.style.alignSelf='flex-start'; botDiv.style.background='rgba(255,215,0,0.2)'; botDiv.style.padding='3px 5px'; botDiv.style.borderRadius='4px'; chatMessages.appendChild(botDiv);
                const response = solveExpression(userMsg);
                botDiv.innerText='AI: '+response;
                chatMessages.scrollTop = chatMessages.scrollHeight;
            }
        });

        minimizeBtn.addEventListener('click', ()=>{calc.style.display='none'; localStorage.setItem(STORAGE_VIS,'hidden');});

        refreshBtn.addEventListener('click', ()=>{
            calc.style.width = DEFAULT_WIDTH+'px';
            calc.style.height = DEFAULT_HEIGHT+'px';
            calc.style.left = DEFAULT_LEFT+'px';
            calc.style.top = DEFAULT_TOP+'px';
            display.style.fontSize='18px';
            display.value='0';
            buttonsContainer.querySelectorAll('button').forEach(b=>{b.style.fontSize='14px'; b.style.padding='10px';});
        });

        const header = calc.querySelector('#calc-header');
        let dragging = false, offsetX=0, offsetY=0;
        header.addEventListener('mousedown', e=>{dragging=true; offsetX=e.clientX-calc.getBoundingClientRect().left; offsetY=e.clientY-calc.getBoundingClientRect().top; calc.style.cursor='grabbing';});
        document.addEventListener('mousemove', e=>{if(dragging){e.preventDefault(); calc.style.left=(e.clientX-offsetX)+'px'; calc.style.top=(e.clientY-offsetY)+'px';}});
        document.addEventListener('mouseup', ()=>{if(dragging){dragging=false; calc.style.cursor='grab'; localStorage.setItem(STORAGE_POS, JSON.stringify({left:parseInt(calc.style.left), top:parseInt(calc.style.top),width:parseInt(calc.style.width), height:parseInt(calc.style.height)}));}});

        document.addEventListener('keydown', e=>{if(e.ctrlKey && e.key==='l'){calc.style.left = DEFAULT_LEFT+'px'; calc.style.top = DEFAULT_TOP+'px'; calc.style.width = DEFAULT_WIDTH+'px'; calc.style.height = DEFAULT_HEIGHT+'px'; display.value='0'; localStorage.removeItem(STORAGE_POS);}});

        const toggleBtn = document.createElement('button');
        toggleBtn.innerText='🧮 Calculator';
        Object.assign(toggleBtn.style,{position:'fixed', top:'50%', left:'10px', transform:'translateY(-50%)', zIndex:'10000000', padding:'10px 15px', fontSize:'14px', background:'linear-gradient(135deg,#d946ef,#8b5cf6)', color:'white', border:'none', borderRadius:'5px', cursor:'pointer'});
        toggleBtn.addEventListener('click', ()=>{if(calc.style.display==='none'){calc.style.display='flex'; localStorage.setItem(STORAGE_VIS,'visible');}else{calc.style.display='none'; localStorage.setItem(STORAGE_VIS,'hidden');}});
        document.body.appendChild(toggleBtn);
    }

    createCalculator();
})();