cool calculator

Draggable, resizable, fullscreen, pink-purple gradient calculator with exponentiation, sqrt, parentheses, and persistent state by Leon Luk.

目前為 2025-10-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name         cool calculator
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Draggable, resizable, fullscreen, pink-purple gradient calculator with exponentiation, sqrt, parentheses, and persistent state by Leon Luk.
// @author       Leon Luk
// @match        *://*/*
// @grant        none
// @license      Proprietary
// ==/UserScript==

(function() {
    'use strict';

    const STORAGE_POS = 'leonluk_calc_pos';
    const STORAGE_OPEN = 'leonluk_calc_open';
    const STORAGE_FULLSCREEN = 'leonluk_calc_fullscreen';

    const button = document.createElement('button');
    button.innerText = '🧮 Calculator';
    Object.assign(button.style, {
        position: 'fixed',
        top: '50%',
        left: '10px',
        transform: 'translateY(-50%)',
        zIndex: '999999',
        padding: '10px 15px',
        fontSize: '14px',
        background: 'linear-gradient(135deg,#d946ef,#7c3aed)',
        color: 'white',
        border: 'none',
        borderRadius: '8px',
        cursor: 'pointer',
        boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
        transition: 'opacity 0.3s ease'
    });
    button.onmouseenter = () => button.style.opacity = '0.8';
    button.onmouseleave = () => button.style.opacity = '1';
    document.body.appendChild(button);

    const calc = document.createElement('div');
    Object.assign(calc.style, {
        position: 'fixed',
        top: '50%',
        left: '-300px',
        width: '300px',
        height: 'auto',
        padding: '15px',
        background: 'linear-gradient(135deg,#ec4899,#8b5cf6)',
        color: 'white',
        borderRadius: '10px',
        boxShadow: '4px 0 15px rgba(0,0,0,0.6)',
        zIndex: '1000000',
        transition: 'left 0.5s cubic-bezier(0.25,1,0.3,1), width 0.3s, height 0.3s',
        fontFamily: 'monospace',
        opacity: '0.97',
        cursor: 'grab',
        overflow: 'hidden'
    });
    document.body.appendChild(calc);

    const savedPos = JSON.parse(localStorage.getItem(STORAGE_POS));
    const savedOpen = localStorage.getItem(STORAGE_OPEN);
    const savedFullscreen = localStorage.getItem(STORAGE_FULLSCREEN);

    if (savedPos) {
        calc.style.left = savedPos.left + 'px';
        calc.style.top = savedPos.top + 'px';
        calc.style.width = (savedPos.width || 300) + 'px';
        calc.style.height = (savedPos.height || 'auto') + 'px';
        calc.style.transform = '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="fullscreen-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" 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;">
        <div id="calc-buttons" style="display:grid;grid-template-columns:repeat(5,1fr);gap:5px;margin-bottom:5px;"></div>
        <div style="text-align:right;font-size:10px;opacity:0.8;">by Leon Luk</div>
    `;

    const display = calc.querySelector('#calc-display');
    const buttonsContainer = calc.querySelector('#calc-buttons');
    const minimizeBtn = calc.querySelector('#minimize-btn');
    const fullscreenBtn = calc.querySelector('#fullscreen-btn');

    const buttons = [
        '7','8','9','/','C',
        '4','5','6','*','(',
        '1','2','3','-',')',
        '0','.','=','+','^',
        '√'
    ];

    buttons.forEach(key => {
        const btn = document.createElement('button');
        btn.innerText = key;
        Object.assign(btn.style, {
            padding: '10px',
            fontSize: '16px',
            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', () => {
            if(key === 'C') { display.value = ''; }
            else if(key === '=') {
                try {
                    // Replace ^ with ** for exponentiation
                    const expValue = display.value.replace(/\^/g,'**').replace(/√/g,'Math.sqrt');
                    display.value = eval(expValue);
                } catch {
                    display.value = 'Error';
                }
            } else {
                display.value += key;
            }
        });
        buttonsContainer.appendChild(btn);
    });

    let isOpen = false;
    if(savedOpen === 'true'){ isOpen = true; calc.style.left = '70px'; }
    let isFullscreen = false;
    if(savedFullscreen === 'true'){ 
        isFullscreen = true;
        calc.style.left = '0';
        calc.style.top = '0';
        calc.style.width = '100%';
        calc.style.height = '100%';
    }

    button.addEventListener('click', () => {
        isOpen = !isOpen;
        calc.style.left = isOpen ? '70px' : '-300px';
        localStorage.setItem(STORAGE_OPEN, isOpen);
    });

    minimizeBtn.addEventListener('click', () => {
        calc.style.left = '-300px';
        isOpen = false;
        localStorage.setItem(STORAGE_OPEN, isOpen);
    });

    fullscreenBtn.addEventListener('click', () => {
        isFullscreen = !isFullscreen;
        if(isFullscreen){
            calc.style.left = '0';
            calc.style.top = '0';
            calc.style.width = '100%';
            calc.style.height = '100%';
        } else {
            const pos = JSON.parse(localStorage.getItem(STORAGE_POS)) || {left:70,top:50,width:300,height:'auto'};
            calc.style.left = pos.left + 'px';
            calc.style.top = pos.top + 'px';
            calc.style.width = (pos.width || 300) + 'px';
            calc.style.height = (pos.height || 'auto') + 'px';
        }
        localStorage.setItem(STORAGE_FULLSCREEN, isFullscreen);
    });

    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.transition = 'none';
        calc.style.cursor = 'grabbing';
    });

    document.addEventListener('mousemove', e => {
        if(!dragging) return;
        e.preventDefault();
        if(!isFullscreen){
            const left = e.clientX - offsetX;
            const top = e.clientY - offsetY;
            calc.style.left = left + 'px';
            calc.style.top = top + 'px';
            calc.style.transform = 'none';
        }
    });

    document.addEventListener('mouseup', () => {
        if(dragging){
            dragging = false;
            calc.style.cursor = 'grab';
            calc.style.transition = 'left 0.5s cubic-bezier(0.25,1,0.3,1)';
            localStorage.setItem(STORAGE_POS, JSON.stringify({
                left: parseInt(calc.style.left),
                top: parseInt(calc.style.top),
                width: parseInt(calc.style.width),
                height: calc.style.height
            }));
        }
    });

    // Resizer
    const resizer = document.createElement('div');
    Object.assign(resizer.style,{
        width:'10px', height:'10px', position:'absolute',
        right:0, bottom:0, cursor:'se-resize', background:'rgba(255,255,255,0.2)'
    });
    calc.appendChild(resizer);

    let resizing = false;
    resizer.addEventListener('mousedown', e => { resizing = true; e.preventDefault(); });

    document.addEventListener('mousemove', e => {
        if(!resizing) return;
        e.preventDefault();
        const w = e.clientX - calc.getBoundingClientRect().left;
        const h = e.clientY - calc.getBoundingClientRect().top;
        calc.style.width = w + 'px';
        calc.style.height = h + 'px';
    });

    document.addEventListener('mouseup', () => {
        if(resizing){
            resizing = false;
            localStorage.setItem(STORAGE_POS, JSON.stringify({
                left: parseInt(calc.style.left),
                top: parseInt(calc.style.top),
                width: parseInt(calc.style.width),
                height: calc.style.height
            }));
        }
    });

})();