Drawaria Canvas Phone

A cellphone on the canvas: EVERYTHING universally visible, text and icons only with lines. Now with a draggable control menu.

// ==UserScript==
// @name        Drawaria Canvas Phone
// @namespace   http://tampermonkey.net/
// @version     2.3
// @description A cellphone on the canvas: EVERYTHING universally visible, text and icons only with lines. Now with a draggable control menu.
// @author      YouTubeDrawaria
// @match       https://drawaria.online/*
// @grant       none
// @license     MIT
// @icon        https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @run-at      document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- LETTER PATHS (clean & unified) ---
    const letterPaths = {
        a: [[10, 40], [20, 0], [30, 40], [25, 20], [15, 20]],
        b: [[0, 0], [0, 40], [15, 40], [20, 35], [20, 25], [15, 20], [0, 20], [15, 20], [20, 15], [20, 5], [15, 0], [0, 0]],
        c: [[20, 0], [0, 0], [0, 40], [20, 40]],
        d: [[0, 0], [0, 40], [15, 40], [30, 20], [15, 0], [0, 0]],
        e: [[30, 0], [0, 0], [0, 20], [20, 20], [0, 20], [0, 40], [30, 40]],
        f: [[0, 0], [0, 40], [20, 40], null, [0, 20], [15, 20]],
        g: [[30, 10], [20, 0], [10, 0], [0, 10], [0, 30], [10, 40], [20, 40], [30, 30], [20, 20]],
        i: [[10, 0], [10, 40]],
        j: [[20, 0], [20, 40], [10, 40], [0, 30]],
        l: [[0, 0], [0, 40], [20, 40]],
        m: [[0, 40], [0, 0], [10, 20], [20, 0], [20, 40]],
        n: [[0, 40], [0, 0], [20, 40], [20, 0]],
        o: [[10, 0], [20, 0], [30, 10], [30, 30], [20, 40], [10, 40], [0, 30], [0, 10], [10, 0]],
        p: [[0, 40], [0, 0], [10, 0], [20, 10], [10, 20], [0, 20]],
        r: [[0, 40], [0, 0], [20, 0], [20, 20], [0, 20], [20, 40]],
        s: [[20, 0], [10, 0], [0, 10], [20, 20], [30, 30], [20, 40], [10, 40], [0, 30]],
        t: [[10, 0], [10, 40], null, [1, 0], [20, 0]],
        u: [[0, 0], [0, 30], [10, 40], [20, 40], [30, 30], [30, 0]],
        v: [[0, 0], [15, 40], [30, 0]],
        z: [[7.5, 35], [9, 36]],
        ' ': [[0, 0]], // Path vacío para el espacio
        // números:
        0: [[10, 0], [20, 0], [30, 10], [30, 30], [20, 40], [10, 40], [0, 30], [0, 10], [10, 0]],
        1: [[15, 0], [15, 40], null, [15, 0], [10, 10], null, [10, 40], [20, 40]],
        2: [[0, 10], [10, 0], [20, 0], [30, 10], [0, 40], [30, 40]],
        3: [[0, 10], [10, 0], [20, 0], [30, 10], [20, 20], [30, 30], [20, 40], [10, 40], [0, 30]],
        4: [[20, 0], [20, 40], null, [0, 20], [25, 20], null, [0, 20], [20, 0]],
        5: [[30, 0], [0, 0], [0, 20], [20, 20], [30, 30], [20, 40], [10, 40], [0, 30]],
        6: [[30, 10], [20, 0], [10, 0], [0, 10], [0, 30], [10, 40], [20, 40], [30, 30], [20, 20], [10, 20], [0, 20], [0, 10]],
        7: [[0, 0], [30, 0], [15, 40]],
        8: [[15, 0], [25, 10], [15, 20], [5, 10], [15, 0], null, [15, 20], [25, 30], [15, 40], [5, 30], [15, 20]],
        9: [[5, 35], [15, 40], [25, 30], [30, 10], [20, 0], [10, 0], [0, 10], [5, 20], [15, 20], [25, 20], [27.5, 20]],
    };

    function drawLetter(path, startX, startY, fontSize, color, thickness) {
        const scale = fontSize / 40;
        for (let i = 0; i < path.length - 1; i++) {
            if (path[i] === null || path[i + 1] === null) continue;
            const [x1, y1] = path[i], [x2, y2] = path[i + 1];
            drawLineServerLocal(
                startX + x1 * scale, startY + y1 * scale,
                startX + x2 * scale, startY + y2 * scale,
                color, thickness
            );
        }
    }

    function drawText(str, x, y, color, thickness = 2, fontSize = 18) {
        let cx = x;
        for (const char of str) {
            const path = letterPaths[char.toLowerCase()];
            if (path) {
                drawLetter(path, cx, y, fontSize, color, thickness);
            }
            cx += fontSize * 0.6;
        }
    }

    // --- DRAWARIA ADAPTERS ---
    let drawariaSocket = null, drawariaCanvas = null, drawariaCtx = null;
    function waitUntilReady() {
        return new Promise(resolve => {
            const check = () => {
                drawariaCanvas = drawariaCanvas || document.getElementById('canvas');
                if (drawariaCanvas) {
                    drawariaCtx = drawariaCtx || drawariaCanvas.getContext('2d');
                }
                if (!drawariaSocket && window.WebSocket && window.WebSocket.prototype) {
                    const origSend = WebSocket.prototype.send;
                    WebSocket.prototype.send = function(...args) {
                        if (this.url && this.url.includes('drawaria')) {
                            drawariaSocket = this;
                            WebSocket.prototype.send = origSend; // Restore original send
                            resolve();
                        }
                        return origSend.apply(this, args);
                    };
                }
                if (drawariaCanvas && drawariaCtx && drawariaSocket) {
                    resolve();
                } else {
                    setTimeout(check, 250);
                }
            };
            check();
        });
    }

    function drawLineServerLocal(x1, y1, x2, y2, color = '#222', thickness = 3) {
        if (!drawariaSocket || !drawariaCanvas) return;
        const nx1 = (x1 / drawariaCanvas.width).toFixed(4), ny1 = (y1 / drawariaCanvas.height).toFixed(4),
            nx2 = (x2 / drawariaCanvas.width).toFixed(4), ny2 = (y2 / drawariaCanvas.height).toFixed(4);
        const cmd = `42["drawcmd",0,[${nx1},${ny1},${nx2},${ny2},false,${-Math.abs(thickness)},"${color}",0,0,{}]]`;
        drawariaSocket.send(cmd);
        drawariaCtx.save();
        drawariaCtx.strokeStyle = color;
        drawariaCtx.lineWidth = thickness;
        drawariaCtx.lineCap = 'round';
        drawariaCtx.beginPath();
        drawariaCtx.moveTo(x1, y1);
        drawariaCtx.lineTo(x2, y2);
        drawariaCtx.stroke();
        drawariaCtx.restore();
    }

    function drawFilledRect(x, y, w, h, color = '#eee') {
        for (let i = 0; i < h; i += 4) drawLineServerLocal(x, y + i, x + w, y + i, color, 4);
    }

    function drawRectServerLocal(x, y, w, h, color = '#222', thickness = 3) {
        drawLineServerLocal(x, y, x + w, y, color, thickness);
        drawLineServerLocal(x + w, y, x + w, y + h, color, thickness);
        drawLineServerLocal(x + w, y + h, x, y + h, color, thickness);
        drawLineServerLocal(x, y + h, x, y, color, thickness);
    }

    // --- PHONE UI ---
    const phoneW = 220, phoneH = 420;
    function getPhoneRect() {
        if (!drawariaCanvas) return { x: 40, y: 40, w: phoneW, h: phoneH };
        const x = drawariaCanvas.width - phoneW - 32,
            y = drawariaCanvas.height - phoneH - 32;
        return { x, y, w: phoneW, h: phoneH };
    }

    let phoneState = 'home', lastButtons = [], lastCalcInput = "", isPhoneActive = false;

    function drawAppButton(x, y, w, h, action, bgColor, borderColor, icon, iconColor, text, textColor) {
        drawFilledRect(x, y, w, h, bgColor);
        drawRectServerLocal(x, y, w, h, borderColor, 2);
        drawText(icon, x + (w * 0.28), y + 6, iconColor, 3, 23);
        drawText(text, x + 2, y + 30, textColor, 2, 10);
        lastButtons.push({ x, y, w, h, action });
    }

    // --- MAIN RENDER ---
    function renderPhone() {
        if (!drawariaCanvas || !isPhoneActive) return;

        const { x, y, w, h } = getPhoneRect();

        // Fondo y marco del teléfono
        drawFilledRect(x, y, w, h, '#F9F9F9');
        drawRectServerLocal(x, y, w, h, '#222', 8);
        drawFilledRect(x + w / 2 - 30, y + 8, 60, 12, '#AAA'); // notch
        drawFilledRect(x + w / 2 - 20, y + h - 22, 40, 8, '#CCC'); // home btn

        // Renderizado basado en el estado
        switch (phoneState) {
            case 'home':
                drawText("a n d r o i d", x + 28, y + 31, '#17A', 3, 17);
                lastButtons = [];
                drawAppButton(x + 22, y + 79, 45, 45, 'open_football', '#FAFAFF', '#222', 'f', '#173', 't e a m', '#222');
                drawAppButton(x + 89, y + 79, 45, 45, 'open_notes', '#FFFDE9', '#222', 'n', '#962', 'n o t e', '#222');
                drawAppButton(x + 156, y + 79, 45, 45, 'open_calc', '#EAEEFF', '#222', 'c', '#246', 'c a r s', '#222');
                drawAppButton(x + 58, y + 151, 45, 45, 'open_clock', '#F6FFEA', '#222', 'r', '#184', 'r e a d', '#222');
                drawAppButton(x + 125, y + 151, 45, 45, 'open_gallery', '#FAEEFF', '#222', 'g', '#71a', 'g a m e', '#222');
                break;
            case 'football':
                drawText("f u t b o l", x + 50, y + 36, '#173', 3, 18);
                drawText("b o l a", x + 66, y + 100, '#222', 2, 18);
                // "mini balón" con líneas y círculo
                for (let r = 0; r <= 24; r += 3) {
                    const angle = r;
                    drawLineServerLocal(x + w / 2, y + 160, x + w / 2 + Math.cos(angle) * 19, y + 160 + Math.sin(angle) * 19, '#173', 3);
                }
                drawFilledRect(x + w / 2 - 4, y + 160 - 4, 9, 9, '#EEE');
                break;
            case 'notes':
                drawText("n o t a s", x + 71, y + 36, '#962', 3, 18);
                drawFilledRect(x + 36, y + 80, w - 72, 130, '#fffbe0');
                drawRectServerLocal(x + 36, y + 80, w - 72, 130, '#555', 2);
                drawText("t o m a   n o t a !", x + 62, y + 170, "#333", 2, 15);
                break;
            case 'calc':
                drawText("c a l c", x + 68, y + 36, '#246', 3, 18);
                drawFilledRect(x + 31, y + 80, w - 62, 38, '#f4f7fd');
                drawRectServerLocal(x + 31, y + 80, w - 62, 38, '#222', 2);
                drawText(lastCalcInput || "3 + 2 * 6", x + 45, y + 93, '#246', 2, 18);
                // Pad 1-9,+,-
                const bw = 33, bh = 22, pad = 7, cols = 3;
                const numpad = ['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', '+', '-'];
                lastButtons = [];
                for (let i = 0; i < numpad.length; i++) {
                    const bx = x + 38 + ((i % cols) * (bw + pad)), by = y + 132 + (Math.floor(i / cols) * (bh + pad));
                    drawFilledRect(bx, by, bw, bh, '#eef2f4');
                    drawRectServerLocal(bx, by, bw, bh, '#222', 2);
                    drawText(numpad[i], bx + 12, by + 3, '#246', 14, 14);
                    lastButtons.push({ x: bx, y: by, w: bw, h: bh, action: 'calcpress', val: numpad[i] });
                }
                // Igual y CE
                const eqx = x + 38, eqy = y + 132 + (4 * (bh + pad));
                drawFilledRect(eqx, eqy, bw + pad, bh, '#86b089');
                drawText('=', eqx + 19, eqy + 3, '#fff', 2, 17);
                lastButtons.push({ x: eqx, y: eqy, w: bw + pad, h: bh, action: 'equals' });
                const cex = eqx + (bw + pad) * 2, cey = eqy;
                drawFilledRect(cex, cey, bw + pad, bh, '#df3434');
                drawText('c e', cex + 11, cey + 3, '#fff', 2, 17);
                lastButtons.push({ x: cex, y: cey, w: bw + pad, h: bh, action: 'ce' });
                break;
            case 'clock':
                drawText("r e l o j", x + 64, y + 36, '#184', 3, 18);
                const cx = x + w / 2, cy = y + 155, radius = 44;
                for (let i = 0; i < 60; i++) {
                    if (i % 5 === 0) {
                        const angle = Math.PI * 2 * i / 60 - Math.PI / 2;
                        drawLineServerLocal(cx + Math.cos(angle) * radius, cy + Math.sin(angle) * radius,
                            cx + Math.cos(angle) * (radius - 7), cy + Math.sin(angle) * (radius - 7), '#262', 3);
                    }
                }
                const date = new Date();
                const h = date.getHours() % 12, m = date.getMinutes(), s = date.getSeconds();
                const ah = Math.PI * 2 * (h + m / 60) / 12 - Math.PI / 2;
                const am = Math.PI * 2 * m / 60 - Math.PI / 2;
                const as = Math.PI * 2 * s / 60 - Math.PI / 2;
                drawLineServerLocal(cx, cy, cx + Math.cos(ah) * 22, cy + Math.sin(ah) * 22, '#222', 7);
                drawLineServerLocal(cx, cy, cx + Math.cos(am) * 32, cy + Math.sin(am) * 32, '#262', 5);
                drawLineServerLocal(cx, cy, cx + Math.cos(as) * 38, cy + Math.sin(as) * 38, '#b81f26', 2);
                break;
            case 'gallery':
                drawText("g a l e r", x + 74, y + 36, '#71a', 3, 18);
                const galleryImgs = [{ bg: '#ffe', border: '#e5c' }, { bg: '#ddf', border: '#8cf' }, { bg: '#f8f', border: '#db5' }];
                for (let g = 0; g < galleryImgs.length; g++) {
                    const gx = x + 40 + (g * 39), gy = y + 75;
                    drawFilledRect(gx, gy, 32, 39, galleryImgs[g].bg);
                    drawRectServerLocal(gx, gy, 32, 39, galleryImgs[g].border, 2);
                    drawText("g", gx + 13, gy + 9, "#222", 2, 17);
                }
                drawText("O b r a s !", x + 75, y + 140, "#71a", 2, 16);
                break;
        }

        // Botón 'volver' común a todas las apps
        if (phoneState !== 'home') {
            const btnColor = (phoneState === 'football') ? '#272' : (phoneState === 'notes' ? '#962' : (phoneState === 'calc' ? '#246' : (phoneState === 'clock' ? '#184' : '#71a')));
            lastButtons.push({ x: x + 38, y: y + h - 56, w: w - 76, h: 32, action: 'home' });
            drawFilledRect(x + 38, y + h - 56, w - 76, 32, '#DDD');
            drawRectServerLocal(x + 38, y + h - 56, w - 76, 32, '#222', 2);
            drawText("v o l v e r", x + 61, y + h - 50, btnColor, 2, 17);
        }
    }

    // --- HANDLE CLICK ---
    function getMouseCanvasCoords(e) {
        const rect = drawariaCanvas.getBoundingClientRect();
        return {
            x: (e.clientX - rect.left) * (drawariaCanvas.width / rect.width),
            y: (e.clientY - rect.top) * (drawariaCanvas.height / rect.height)
        };
    }

    function onCanvasClick(e) {
        if (!isPhoneActive) return;

        const { x: cx, y: cy } = getMouseCanvasCoords(e);
        for (const btn of lastButtons) {
            if (cx >= btn.x && cx <= btn.x + btn.w && cy >= btn.y && cy <= btn.y + btn.h) {
                switch (btn.action) {
                    case 'open_football': phoneState = 'football'; break;
                    case 'open_notes': phoneState = 'notes'; break;
                    case 'open_calc': phoneState = 'calc'; break;
                    case 'open_clock': phoneState = 'clock'; break;
                    case 'open_gallery': phoneState = 'gallery'; break;
                    case 'home': phoneState = 'home'; break;
                    case 'calcpress':
                        lastCalcInput += btn.val;
                        break;
                    case 'equals':
                        try {
                            lastCalcInput = String(eval(lastCalcInput.replace(/--/g, '+')));
                        } catch {
                            lastCalcInput = "ERR";
                        }
                        break;
                    case 'ce':
                        lastCalcInput = "";
                        break;
                }
                renderPhone();
                return;
            }
        }
    }

    // --- MAIN BOOT ---
    waitUntilReady().then(() => {
        // Inicializar el menú de control
        createControlMenu();

        // Renderizar el teléfono y el menú
        const renderLoop = () => {
            renderPhone();
            // El menú se mantiene en el DOM, no necesita renderizarse en el canvas
        };

        setInterval(renderLoop, 2800);
        drawariaCanvas.addEventListener('click', onCanvasClick);

        window.addEventListener('keydown', (e) => {
            if (e.key === 'h' && isPhoneActive) {
                phoneState = 'home';
                renderPhone();
            }
        });
    });

    // --- CONTROL MENU ---
    function createControlMenu() {
        // Crear el elemento del menú
        const menu = document.createElement('div');
        menu.id = 'drawaria-phone-menu';
        menu.style.cssText = `
            position: absolute;
            top: 20px;
            left: 20px;
            width: 200px;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            border-radius: 12px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
            color: #fff;
            font-family: Arial, sans-serif;
            z-index: 10000;
            padding: 15px;
            cursor: move;
        `;
        menu.innerHTML = `
            <h4 style="margin: 0 0 10px; font-weight: bold; text-align: center;">Phone Control</h4>
            <div style="display: flex; flex-direction: column; gap: 10px;">
                <button id="toggle-phone" style="
                    padding: 8px;
                    border: none;
                    border-radius: 8px;
                    background-color: #e2a04a;
                    color: white;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: background-color 0.3s;
                ">Turn On</button>
                <button id="reset-phone" style="
                    padding: 8px;
                    border: none;
                    border-radius: 8px;
                    background-color: #8c73d9;
                    color: white;
                    font-size: 14px;
                    font-weight: bold;
                    cursor: pointer;
                    transition: background-color 0.3s;
                ">Reset Home</button>
            </div>
        `;
        document.body.appendChild(menu);

        // Hacer el menú arrastrable
        let isDragging = false;
        let offset = { x: 0, y: 0 };
        const header = menu.querySelector('h4');

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offset.x = e.clientX - menu.offsetLeft;
            offset.y = e.clientY - menu.offsetTop;
            menu.style.cursor = 'grabbing';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                menu.style.left = `${e.clientX - offset.x}px`;
                menu.style.top = `${e.clientY - offset.y}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            menu.style.cursor = 'move';
        });

        // Lógica de los botones
        const toggleButton = document.getElementById('toggle-phone');
        const resetButton = document.getElementById('reset-phone');

        toggleButton.addEventListener('click', () => {
            isPhoneActive = !isPhoneActive;
            if (isPhoneActive) {
                toggleButton.textContent = 'Turn Off';
                toggleButton.style.backgroundColor = '#4a90e2';
                renderPhone();
            } else {
                toggleButton.textContent = 'Turn On';
                toggleButton.style.backgroundColor = '#e2a04a';
                // Borrar el teléfono del canvas al apagarlo
                drawLineServerLocal(0, 0, 0, 0, '#fff', 9999);
            }
        });

        resetButton.addEventListener('click', () => {
            phoneState = 'home';
            if (isPhoneActive) {
                renderPhone();
            }
        });
    }

})();