Torn Custom Navigation Buttons

Floating nav buttons with drag, edit, hotkeys, backup, and persistent minimize.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Torn Custom Navigation Buttons
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  Floating nav buttons with drag, edit, hotkeys, backup, and persistent minimize.
// @author       duckybeks
// @match        https://www.torn.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    var STORAGE_KEY_BUTTONS = 'torn_custom_buttons';
    var STORAGE_KEY_POS = 'torn_custom_buttons_pos';
    var STORAGE_KEY_BACKUP = 'torn_custom_buttons_backup';
    var STORAGE_KEY_MINIMIZED = 'torn_nav_minimized';

    var DEFAULT_BUTTONS = [
        { id: 1, label: 'Home', url: 'https://www.torn.com/index.php', hotkey: 'H' },
        { id: 2, label: 'Bazaar', url: 'https://www.torn.com/bazaar.php', hotkey: 'B' },
        { id: 3, label: 'Item Market', url: 'https://www.torn.com/page.php?sid=ItemMarket#/market/view=category&categoryName=Most%20Popular', hotkey: 'I' },
        { id: 4, label: 'Trades', url: 'https://www.torn.com/trade.php', hotkey: 'T' },
        { id: 5, label: 'Events', url: 'https://www.torn.com/events.php', hotkey: 'E' },
    ];

    var buttons = loadButtons();
    var nextId = buttons.length > 0 ? Math.max(buttons.map(function(b) { return b.id; })) + 1 : 1;
    var container = null;
    var isDragging = false;

    function loadButtons() {
        try {
            var saved = localStorage.getItem(STORAGE_KEY_BUTTONS);
            if (saved) {
                var parsed = JSON.parse(saved);
                if (Array.isArray(parsed) && parsed.length > 0) return parsed;
            }
        } catch (e) {}

        try {
            var backup = localStorage.getItem(STORAGE_KEY_BACKUP);
            if (backup) {
                var parsed = JSON.parse(backup);
                if (Array.isArray(parsed) && parsed.length > 0) {
                    localStorage.setItem(STORAGE_KEY_BUTTONS, backup);
                    return parsed;
                }
            }
        } catch (e) {}

        return JSON.parse(JSON.stringify(DEFAULT_BUTTONS));
    }

    function saveButtons(data) {
        localStorage.setItem(STORAGE_KEY_BUTTONS, JSON.stringify(data));
        localStorage.setItem(STORAGE_KEY_BACKUP, JSON.stringify(data));
    }

    function loadPosition() {
        try {
            var saved = localStorage.getItem(STORAGE_KEY_POS);
            if (saved) {
                var pos = JSON.parse(saved);
                if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') return pos;
            }
        } catch (e) {}
        return null;
    }

    function savePosition(left, top) {
        localStorage.setItem(STORAGE_KEY_POS, JSON.stringify({ left: left, top: top }));
    }

    function isMinimized() {
        return localStorage.getItem(STORAGE_KEY_MINIMIZED) === 'true';
    }

    function setMinimized(state) {
        localStorage.setItem(STORAGE_KEY_MINIMIZED, state ? 'true' : 'false');
    }

    function exportData() {
        var data = {
            buttons: buttons,
            position: loadPosition(),
            exported: new Date().toISOString()
        };
        var blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'torn_buttons_backup_' + new Date().toISOString().slice(0,10) + '.json';
        a.click();
        URL.revokeObjectURL(url);
    }

    function importData() {
        var input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';
        input.onchange = function(e) {
            var file = e.target.files[0];
            var reader = new FileReader();
            reader.onload = function(event) {
                try {
                    var data = JSON.parse(event.target.result);
                    if (data.buttons && Array.isArray(data.buttons)) {
                        buttons = data.buttons;
                        saveButtons(buttons);
                        if (data.position) {
                            savePosition(data.position.left, data.position.top);
                        }
                        setMinimized(false);
                        rebuildUI();
                        alert('Restored ' + buttons.length + ' buttons.');
                    } else {
                        alert('Invalid backup file.');
                    }
                } catch (err) {
                    alert('Error reading file: ' + err.message);
                }
            };
            reader.readAsText(file);
        };
        input.click();
    }

    function resetData() {
        if (confirm('Reset all buttons to default?')) {
            buttons = JSON.parse(JSON.stringify(DEFAULT_BUTTONS));
            nextId = buttons.length > 0 ? Math.max(buttons.map(function(b) { return b.id; })) + 1 : 1;
            saveButtons(buttons);
            localStorage.removeItem(STORAGE_KEY_POS);
            setMinimized(false);
            rebuildUI();
            alert('Buttons reset to default.');
        }
    }

    document.addEventListener('keydown', function(e) {
        if (isMinimized()) return;
        try {
            var target = e.target;
            if (target.tagName === 'INPUT' || 
                target.tagName === 'TEXTAREA' || 
                target.isContentEditable ||
                target.closest('[contenteditable="true"], [role="textbox"]')) {
                return;
            }
            var key = e.key.toUpperCase();
            var found = buttons.find(function(btn) {
                return btn.hotkey && btn.hotkey.toUpperCase() === key;
            });
            if (found) {
                e.preventDefault();
                window.location.href = found.url;
            }
        } catch(err) {}
    });

    function createFloatingButtons() {
        if (document.getElementById('torn-custom-nav')) return;

        container = document.createElement('div');
        container.id = 'torn-custom-nav';
        container.style.cssText =
            'position: fixed; z-index: 9999; display: flex; flex-direction: column; gap: 6px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; cursor: default; user-select: none; ' +
            'background: rgba(26, 26, 30, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); ' +
            'border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 8px 6px; ' +
            'box-shadow: 0 8px 32px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05); min-width: 140px;';

        var pos = loadPosition();
        if (pos) {
            container.style.left = pos.left + 'px';
            container.style.top = pos.top + 'px';
        } else {
            container.style.bottom = '80px';
            container.style.right = '20px';
        }

        var dragHandle = document.createElement('div');
        dragHandle.style.cssText =
            'width: 24px; height: 3px; background: rgba(255,255,255,0.15); border-radius: 4px; margin: 0 auto 4px auto; cursor: grab; transition: background 0.2s;';
        dragHandle.title = 'Drag to move';
        dragHandle.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(255,255,255,0.3)';
        });
        dragHandle.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.15)';
        });
        container.appendChild(dragHandle);

        renderButtons();

        var controlRow = document.createElement('div');
        controlRow.style.cssText =
            'display: flex; gap: 4px; justify-content: center; margin-top: 4px; padding-top: 6px; border-top: 1px solid rgba(255,255,255,0.06);';

        var toggleBtn = document.createElement('button');
        toggleBtn.textContent = isMinimized() ? '☰' : '✕';
        toggleBtn.style.cssText =
            'background: rgba(255,255,255,0.06); color: #888; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; transition: all 0.2s;';
        toggleBtn.addEventListener('mouseenter', function() {
            if (this.textContent === '✕') {
                this.style.background = 'rgba(200,50,50,0.2)';
                this.style.color = '#ff6b6b';
            } else {
                this.style.background = 'rgba(50,150,255,0.15)';
                this.style.color = '#5b9aff';
            }
        });
        toggleBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.06)';
            this.style.color = '#888';
        });
        toggleBtn.addEventListener('click', function() {
            if (isMinimized()) {
                setMinimized(false);
                container.querySelectorAll('.nav-btn:not(.no-toggle)').forEach(function(b) {
                    b.style.display = 'flex';
                });
                container.querySelectorAll('.nav-btn.no-toggle').forEach(function(b) {
                    b.style.display = 'flex';
                });
                exportBtn.style.display = 'flex';
                importBtn.style.display = 'flex';
                resetBtn.style.display = 'flex';
                toggleBtn.textContent = '✕';
            } else {
                setMinimized(true);
                container.querySelectorAll('.nav-btn:not(.no-toggle)').forEach(function(b) {
                    b.style.display = 'none';
                });
                container.querySelectorAll('.nav-btn.no-toggle').forEach(function(b) {
                    b.style.display = 'none';
                });
                exportBtn.style.display = 'none';
                importBtn.style.display = 'none';
                resetBtn.style.display = 'none';
                toggleBtn.textContent = '☰';
            }
        });

        var exportBtn = document.createElement('button');
        exportBtn.textContent = '💾';
        exportBtn.title = 'Backup';
        exportBtn.style.cssText =
            'background: rgba(255,255,255,0.06); color: #888; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; transition: all 0.2s;';
        exportBtn.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(50,150,255,0.15)';
            this.style.color = '#5b9aff';
        });
        exportBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.06)';
            this.style.color = '#888';
        });
        exportBtn.addEventListener('click', exportData);

        var importBtn = document.createElement('button');
        importBtn.textContent = '📂';
        importBtn.title = 'Restore';
        importBtn.style.cssText =
            'background: rgba(255,255,255,0.06); color: #888; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; transition: all 0.2s;';
        importBtn.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(50,200,100,0.15)';
            this.style.color = '#5bff8a';
        });
        importBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.06)';
            this.style.color = '#888';
        });
        importBtn.addEventListener('click', importData);

        var resetBtn = document.createElement('button');
        resetBtn.textContent = '↺';
        resetBtn.title = 'Reset to default';
        resetBtn.style.cssText =
            'background: rgba(255,255,255,0.06); color: #888; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; transition: all 0.2s;';
        resetBtn.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(200,50,50,0.2)';
            this.style.color = '#ff6b6b';
        });
        resetBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.06)';
            this.style.color = '#888';
        });
        resetBtn.addEventListener('click', resetData);

        controlRow.appendChild(toggleBtn);
        controlRow.appendChild(exportBtn);
        controlRow.appendChild(importBtn);
        controlRow.appendChild(resetBtn);
        container.appendChild(controlRow);

        if (isMinimized()) {
            container.querySelectorAll('.nav-btn:not(.no-toggle)').forEach(function(b) {
                b.style.display = 'none';
            });
            container.querySelectorAll('.nav-btn.no-toggle').forEach(function(b) {
                b.style.display = 'none';
            });
            exportBtn.style.display = 'none';
            importBtn.style.display = 'none';
            resetBtn.style.display = 'none';
            toggleBtn.textContent = '☰';
        }

        var isDraggingPanel = false;
        var startX, startY, origLeft, origTop;

        dragHandle.addEventListener('mousedown', function(e) {
            isDraggingPanel = true;
            var rect = container.getBoundingClientRect();
            startX = e.clientX;
            startY = e.clientY;
            origLeft = rect.left;
            origTop = rect.top;
            container.style.cursor = 'grabbing';
            e.preventDefault();
        });

        document.addEventListener('mousemove', function(e) {
            if (!isDraggingPanel) return;
            var dx = e.clientX - startX;
            var dy = e.clientY - startY;
            var newLeft = origLeft + dx;
            var newTop = origTop + dy;
            if (newTop < 0) newTop = 0;
            container.style.left = newLeft + 'px';
            container.style.top = newTop + 'px';
            container.style.bottom = 'auto';
            container.style.right = 'auto';
        });

        document.addEventListener('mouseup', function() {
            if (isDraggingPanel) {
                isDraggingPanel = false;
                container.style.cursor = 'default';
                var rect = container.getBoundingClientRect();
                savePosition(rect.left, rect.top);
            }
        });

        document.body.appendChild(container);
    }

    function renderButtons() {
        var oldBtns = container.querySelectorAll('.nav-btn');
        oldBtns.forEach(function(b) { b.remove(); });

        for (var i = 0; i < buttons.length; i++) {
            var btn = buttons[i];

            var wrapper = document.createElement('div');
            wrapper.className = 'nav-btn';
            wrapper.dataset.btnId = btn.id;
            wrapper.style.cssText =
                'display: flex; align-items: center; gap: 2px; background: rgba(255,255,255,0.04); border-radius: 8px; transition: all 0.15s; padding: 2px;';

            wrapper.addEventListener('mouseenter', function() {
                this.style.background = 'rgba(255,255,255,0.08)';
            });
            wrapper.addEventListener('mouseleave', function() {
                this.style.background = 'rgba(255,255,255,0.04)';
            });

            var dragHandleBtn = document.createElement('span');
            dragHandleBtn.textContent = '⠿';
            dragHandleBtn.title = 'Drag to reorder';
            dragHandleBtn.style.cssText =
                'padding: 4px 4px 4px 6px; cursor: grab; color: rgba(255,255,255,0.15); font-size: 12px; user-select: none; transition: color 0.2s; display: flex; align-items: center;';
            dragHandleBtn.addEventListener('mouseenter', function() {
                this.style.color = 'rgba(255,255,255,0.4)';
            });
            dragHandleBtn.addEventListener('mouseleave', function() {
                this.style.color = 'rgba(255,255,255,0.15)';
            });

            var navBtn = document.createElement('div');
            navBtn.style.cssText =
                'color: #ddd; padding: 4px 6px 4px 2px; cursor: pointer; font-size: 13px; font-weight: 500; user-select: none; flex: 1; letter-spacing: 0.2px; display: flex; align-items: center; gap: 6px;';

            var badge = document.createElement('span');
            badge.textContent = btn.hotkey || '';
            badge.style.cssText =
                'font-size: 10px; font-weight: 600; color: rgba(255,255,255,0.25); background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 4px; margin-left: auto; letter-spacing: 0.5px; font-family: "SF Mono", monospace;';

            if (!btn.hotkey) {
                badge.style.display = 'none';
            }

            navBtn.appendChild(document.createTextNode(btn.label));
            navBtn.appendChild(badge);

            (function(currentBtn) {
                navBtn.addEventListener('click', function(e) {
                    if (isDragging) return;
                    window.open(currentBtn.url, '_self');
                });
            })(btn);

            var editBtn = document.createElement('span');
            editBtn.textContent = '✏️';
            editBtn.style.cssText =
                'padding: 4px 4px; cursor: pointer; font-size: 11px; opacity: 0.3; border-radius: 4px; transition: all 0.15s; display: flex; align-items: center;';
            editBtn.addEventListener('mouseenter', function() {
                this.style.opacity = '1';
                this.style.background = 'rgba(50,150,255,0.2)';
                this.style.color = '#5b9aff';
            });
            editBtn.addEventListener('mouseleave', function() {
                this.style.opacity = '0.3';
                this.style.background = 'transparent';
                this.style.color = 'inherit';
            });
            (function(btnId) {
                editBtn.addEventListener('click', function(e) {
                    e.stopPropagation();
                    openEditor(btnId);
                });
            })(btn.id);

            var delBtn = document.createElement('span');
            delBtn.textContent = '🗑️';
            delBtn.style.cssText =
                'padding: 4px 4px; cursor: pointer; font-size: 11px; opacity: 0.3; border-radius: 4px; transition: all 0.15s; display: flex; align-items: center;';
            delBtn.addEventListener('mouseenter', function() {
                this.style.opacity = '1';
                this.style.background = 'rgba(200,50,50,0.2)';
                this.style.color = '#ff6b6b';
            });
            delBtn.addEventListener('mouseleave', function() {
                this.style.opacity = '0.3';
                this.style.background = 'transparent';
                this.style.color = 'inherit';
            });
            (function(btnId, btnLabel) {
                delBtn.addEventListener('click', function(e) {
                    e.stopPropagation();
                    if (confirm('Delete "' + btnLabel + '"?')) {
                        var newButtons = [];
                        for (var j = 0; j < buttons.length; j++) {
                            if (buttons[j].id !== btnId) {
                                newButtons.push(buttons[j]);
                            }
                        }
                        buttons = newButtons;
                        if (buttons.length > 0) {
                            var maxId = 0;
                            for (var k = 0; k < buttons.length; k++) {
                                if (buttons[k].id > maxId) maxId = buttons[k].id;
                            }
                            nextId = maxId + 1;
                        } else {
                            nextId = 1;
                        }
                        saveButtons(buttons);
                        rebuildUI();
                    }
                });
            })(btn.id, btn.label);

            wrapper.appendChild(dragHandleBtn);
            wrapper.appendChild(navBtn);
            wrapper.appendChild(editBtn);
            wrapper.appendChild(delBtn);
            container.insertBefore(wrapper, container.querySelector('.no-toggle'));

            (function(wrapper) {
                var isDraggingLocal = false;
                var ghost = null;

                dragHandleBtn.addEventListener('mousedown', function(e) {
                    isDraggingLocal = true;
                    isDragging = true;

                    ghost = wrapper.cloneNode(true);
                    ghost.style.cssText =
                        'position: fixed; pointer-events: none; z-index: 99999; opacity: 0.85; background: rgba(30,30,36,0.95); backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 4px 8px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; color: #ddd; font-size: 13px; font-weight: 500; box-shadow: 0 8px 24px rgba(0,0,0,0.5); min-width: 120px; display: flex; align-items: center; gap: 8px;';
                    ghost.style.left = (e.clientX - 20) + 'px';
                    ghost.style.top = (e.clientY - 20) + 'px';
                    ghost.style.width = wrapper.offsetWidth + 'px';
                    document.body.appendChild(ghost);

                    wrapper.style.opacity = '0.3';
                    e.preventDefault();
                });

                document.addEventListener('mousemove', function(e) {
                    if (!isDraggingLocal || !ghost) return;
                    ghost.style.left = (e.clientX - 20) + 'px';
                    ghost.style.top = (e.clientY - 20) + 'px';
                    ghost.style.transform = 'scale(1.02)';

                    var target = document.elementFromPoint(e.clientX, e.clientY);
                    if (target) {
                        var targetWrapper = target.closest('.nav-btn');
                        if (targetWrapper && targetWrapper !== wrapper && !targetWrapper.classList.contains('no-toggle')) {
                            var rect = targetWrapper.getBoundingClientRect();
                            var middle = rect.top + rect.height / 2;
                            var currentIndex = Array.from(container.children).indexOf(wrapper);
                            var targetIndex = Array.from(container.children).indexOf(targetWrapper);
                            if (e.clientY > middle && targetIndex > currentIndex) {
                                container.insertBefore(wrapper, targetWrapper.nextSibling);
                            } else if (e.clientY < middle && targetIndex < currentIndex) {
                                container.insertBefore(wrapper, targetWrapper);
                            }
                        }
                    }
                });

                document.addEventListener('mouseup', function(e) {
                    if (!isDraggingLocal) return;
                    isDraggingLocal = false;
                    isDragging = false;
                    if (ghost) {
                        ghost.remove();
                        ghost = null;
                    }
                    wrapper.style.opacity = '1';

                    var newOrder = [];
                    var btns = container.querySelectorAll('.nav-btn');
                    for (var l = 0; l < btns.length; l++) {
                        var el = btns[l];
                        var id = parseInt(el.dataset.btnId);
                        for (var m = 0; m < buttons.length; m++) {
                            if (buttons[m].id === id) {
                                newOrder.push(buttons[m]);
                                break;
                            }
                        }
                    }
                    if (newOrder.length > 0 && newOrder.length === buttons.length) {
                        buttons = newOrder;
                        saveButtons(buttons);
                    }
                });
            })(wrapper);
        }

        var addWrapper = document.createElement('div');
        addWrapper.className = 'nav-btn no-toggle';
        addWrapper.style.cssText =
            'display: flex; align-items: center; justify-content: center; margin-top: 4px; padding-top: 6px; border-top: 1px solid rgba(255,255,255,0.06);';

        var addBtn = document.createElement('div');
        addBtn.textContent = '+ Add Button';
        addBtn.style.cssText =
            'color: rgba(255,255,255,0.35); padding: 4px 14px; font-size: 11px; font-weight: 500; cursor: pointer; user-select: none; border-radius: 6px; transition: all 0.2s; letter-spacing: 0.3px; background: rgba(255,255,255,0.03); border: 1px dashed rgba(255,255,255,0.06);';
        addBtn.addEventListener('mouseenter', function() {
            this.style.color = 'rgba(255,255,255,0.7)';
            this.style.background = 'rgba(255,255,255,0.06)';
            this.style.borderColor = 'rgba(255,255,255,0.12)';
        });
        addBtn.addEventListener('mouseleave', function() {
            this.style.color = 'rgba(255,255,255,0.35)';
            this.style.background = 'rgba(255,255,255,0.03)';
            this.style.borderColor = 'rgba(255,255,255,0.06)';
        });
        addBtn.addEventListener('click', function() {
            openEditor(null);
        });

        addWrapper.appendChild(addBtn);
        container.appendChild(addWrapper);
    }

    function openEditor(editId) {
        var existing = document.getElementById('torn-editor-modal');
        if (existing) existing.remove();

        var btn = editId ? buttons.find(function(b) { return b.id === editId; }) : null;
        var isEdit = !!btn;

        var overlay = document.createElement('div');
        overlay.id = 'torn-editor-modal';
        overlay.style.cssText =
            'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.65); backdrop-filter: blur(4px); z-index: 99999; display: flex; align-items: center; justify-content: center;';

        var modal = document.createElement('div');
        modal.style.cssText =
            'background: rgba(22,22,28,0.95); backdrop-filter: blur(16px); padding: 28px 32px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.06); max-width: 420px; width: 90%; box-shadow: 0 24px 64px rgba(0,0,0,0.6);';

        var title = document.createElement('h3');
        title.textContent = isEdit ? '✏️ Edit Button' : '➕ Add Button';
        title.style.cssText = 'color: #eee; margin: 0 0 18px 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; font-size: 17px; font-weight: 600; letter-spacing: -0.2px;';

        var labelInput = document.createElement('input');
        labelInput.type = 'text';
        labelInput.placeholder = 'Label';
        labelInput.value = isEdit ? btn.label : '';
        labelInput.style.cssText =
            'width: 100%; padding: 10px 12px; margin-bottom: 10px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: #eee; font-size: 14px; box-sizing: border-box; outline: none; transition: border-color 0.2s;';
        labelInput.addEventListener('focus', function() {
            this.style.borderColor = 'rgba(80,180,255,0.3)';
        });
        labelInput.addEventListener('blur', function() {
            this.style.borderColor = 'rgba(255,255,255,0.08)';
        });

        var urlInput = document.createElement('input');
        urlInput.type = 'text';
        urlInput.placeholder = 'URL';
        urlInput.value = isEdit ? btn.url : '';
        urlInput.style.cssText =
            'width: 100%; padding: 10px 12px; margin-bottom: 10px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: #eee; font-size: 14px; box-sizing: border-box; outline: none; transition: border-color 0.2s;';
        urlInput.addEventListener('focus', function() {
            this.style.borderColor = 'rgba(80,180,255,0.3)';
        });
        urlInput.addEventListener('blur', function() {
            this.style.borderColor = 'rgba(255,255,255,0.08)';
        });

        var hotkeyInput = document.createElement('input');
        hotkeyInput.type = 'text';
        hotkeyInput.placeholder = 'Hotkey (optional)';
        hotkeyInput.value = isEdit ? (btn.hotkey || '') : '';
        hotkeyInput.style.cssText =
            'width: 100%; padding: 10px 12px; margin-bottom: 16px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: #eee; font-size: 14px; box-sizing: border-box; outline: none; transition: border-color 0.2s;';
        hotkeyInput.addEventListener('focus', function() {
            this.style.borderColor = 'rgba(80,180,255,0.3)';
        });
        hotkeyInput.addEventListener('blur', function() {
            this.style.borderColor = 'rgba(255,255,255,0.08)';
        });

        var btnContainer = document.createElement('div');
        btnContainer.style.cssText = 'display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px;';

        var cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.style.cssText =
            'padding: 8px 18px; background: rgba(255,255,255,0.05); border: none; border-radius: 8px; color: #888; cursor: pointer; font-size: 13px; transition: all 0.2s; font-weight: 500;';
        cancelBtn.addEventListener('click', function() { overlay.remove(); });
        cancelBtn.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(255,255,255,0.08)';
            this.style.color = '#aaa';
        });
        cancelBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(255,255,255,0.05)';
            this.style.color = '#888';
        });

        var saveBtn = document.createElement('button');
        saveBtn.textContent = isEdit ? 'Save' : 'Add';
        saveBtn.style.cssText =
            'padding: 8px 22px; background: rgba(80,180,255,0.2); border: none; border-radius: 8px; color: #5b9aff; cursor: pointer; font-size: 13px; transition: all 0.2s; font-weight: 600;';
        saveBtn.addEventListener('click', function() {
            var label = labelInput.value.trim();
            var url = urlInput.value.trim();
            var hotkey = hotkeyInput.value.trim().toUpperCase();

            if (!label || !url) {
                alert('Please fill Label and URL!');
                return;
            }
            if (!url.startsWith('http://') && !url.startsWith('https://')) {
                alert('URL must start with http:// or https://');
                return;
            }
            if (hotkey && hotkey.length > 1) {
                alert('Hotkey must be a single letter!');
                return;
            }

            if (isEdit) {
                var found = buttons.find(function(b) { return b.id === btn.id; });
                if (found) {
                    found.label = label;
                    found.url = url;
                    found.hotkey = hotkey || undefined;
                }
            } else {
                buttons.push({ id: nextId++, label: label, url: url, hotkey: hotkey || undefined });
            }

            saveButtons(buttons);
            overlay.remove();
            rebuildUI();
        });
        saveBtn.addEventListener('mouseenter', function() {
            this.style.background = 'rgba(80,180,255,0.3)';
        });
        saveBtn.addEventListener('mouseleave', function() {
            this.style.background = 'rgba(80,180,255,0.2)';
        });

        btnContainer.appendChild(cancelBtn);
        btnContainer.appendChild(saveBtn);

        modal.appendChild(title);
        modal.appendChild(labelInput);
        modal.appendChild(urlInput);
        modal.appendChild(hotkeyInput);
        modal.appendChild(btnContainer);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        setTimeout(function() { labelInput.focus(); }, 100);
    }

    function rebuildUI() {
        var old = document.getElementById('torn-custom-nav');
        if (old) old.remove();
        createFloatingButtons();
    }

    function init() {
        createFloatingButtons();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();