FolderWaypoints Pro - Instant Navigation

Мгновенная навигация с предзагрузкой страниц (НЕТ ЗАГРУЗКИ!)

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name        FolderWaypoints Pro - Instant Navigation
// @match       *://*/*
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @grant       GM_addStyle
// @version     8.2
// @description Мгновенная навигация с предзагрузкой страниц (НЕТ ЗАГРУЗКИ!)
// @namespace https://greasyfork.org/users/1560293
// ==/UserScript==

(function() {
    'use strict';

    // ========== КОНФИГУРАЦИЯ ==========
    const CONFIG = {
        MAX_FOLDERS: 50,
        MAX_WAYPOINTS_PER_FOLDER: 200,
        NOTIFICATION_DURATION: 2000,
        CAPTURE_TIMEOUT: 10000,
        STORAGE_KEYS: {
            folders: 'folders_data_v4',
            activeFolderId: 'active_folder_id',
            panelVisible: 'panel_visible',
            arrowPosition: 'arrow_position'
        }
    };

    // ========== ХРАНЕНИЕ ==========
    let folders = [];
    let activeFolderId = null;
    let panelVisible = false;
    let currentView = 'folders';
    let currentFolderId = null;
    let isCapturingKey = false;
    let currentCaptureId = null;
    let captureTimeout = null;
    let arrowPosition = { x: 15, y: 15 };
    
    // Для предзагрузки
    let prefetchedUrls = new Set();
    let isPrefetching = false;

    // ========== ФУНКЦИЯ МГНОВЕННОЙ ПРЕДЗАГРУЗКИ ==========
    function instantPrefetch(url) {
        if (!url || prefetchedUrls.has(url)) return;
        
        // Способ 1: link rel="prefetch" (самый быстрый)
        const link = document.createElement('link');
        link.rel = 'prefetch';
        link.href = url;
        link.as = 'document';
        document.head.appendChild(link);
        
        // Способ 2: предзагрузка через fetch (для надежности)
        if ('fetch' in window) {
            fetch(url, { 
                mode: 'no-cors',
                priority: 'low',
                cache: 'force-cache'
            }).catch(() => {});
        }
        
        prefetchedUrls.add(url);
        console.log(`⚡ МГНОВЕННАЯ ПРЕДЗАГРУЗКА: ${url.substring(0, 60)}...`);
    }

    // Предзагружаем ВСЕ точки из папки
    function prefetchAllFolderWaypoints(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;
        
        console.log(`🚀 НАЧИНАЮ ПРЕДЗАГРУЗКУ папки: ${folder.name} (${folder.waypoints.length} точек)`);
        
        folder.waypoints.forEach((waypoint, index) => {
            // Предзагружаем с небольшой задержкой между запросами, чтобы не нагружать
            setTimeout(() => {
                instantPrefetch(waypoint.url);
            }, index * 300);
        });
    }

    // Когда выбрана новая активная папка - сразу предзагружаем все её точки
    function setActiveFolderAndPrefetch(folderId) {
        activeFolderId = folderId;
        saveActiveFolder();
        
        // МГНОВЕННО начинаем предзагрузку всех точек из этой папки
        prefetchAllFolderWaypoints(activeFolderId);
        
        renderFoldersView();
        showNotification(`📌 Активна: ${folders.find(f => f.id === folderId)?.name} (предзагрузка начата)`, '#00ff88');
    }

    // При открытии папки - тоже предзагружаем
    function openFolderAndPrefetch(folderId) {
        currentFolderId = folderId;
        currentView = 'waypoints';
        
        // Предзагружаем точки из открытой папки
        prefetchAllFolderWaypoints(folderId);
        
        renderWaypointsView();
    }

    // ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
    function getCurrentDomain() {
        return window.location.hostname;
    }

    function getCurrentPageTitle() {
        const h1 = document.querySelector('h1')?.textContent?.trim();
        if (h1) return h1;
        const title = document.querySelector('title')?.textContent?.trim();
        if (title) return title.substring(0, 60);
        return window.location.pathname.substring(0, 50) || 'Главная';
    }

    function formatHotkey(hotkey) {
        if (!hotkey) return 'Не назначена';
        const parts = [];
        if (hotkey.ctrl) parts.push('Ctrl');
        if (hotkey.alt) parts.push('Alt');
        if (hotkey.shift) parts.push('Shift');
        if (hotkey.meta) parts.push('Win');
        if (hotkey.key) {
            let key = hotkey.key;
            const keyMap = {
                ' ': 'Space', 'Enter': 'Enter', 'Tab': 'Tab', 'Escape': 'Esc',
                'ArrowUp': '↑', 'ArrowDown': '↓', 'ArrowLeft': '←', 'ArrowRight': '→',
                'Delete': 'Del', 'Insert': 'Ins', 'Home': 'Home', 'End': 'End',
                'PageUp': 'PgUp', 'PageDown': 'PgDn',
                'F1': 'F1', 'F2': 'F2', 'F3': 'F3', 'F4': 'F4',
                'F5': 'F5', 'F6': 'F6', 'F7': 'F7', 'F8': 'F8',
                'F9': 'F9', 'F10': 'F10', 'F11': 'F11', 'F12': 'F12'
            };
            key = keyMap[key] || (key.length === 1 ? key.toUpperCase() : key);
            parts.push(key);
        }
        return parts.join(' + ');
    }

    // ========== УПРАВЛЕНИЕ ДАННЫМИ ==========
    function loadData() {
        try {
            const savedFolders = GM_getValue(CONFIG.STORAGE_KEYS.folders, '[]');
            folders = JSON.parse(savedFolders);
            activeFolderId = GM_getValue(CONFIG.STORAGE_KEYS.activeFolderId, null);
            panelVisible = GM_getValue(CONFIG.STORAGE_KEYS.panelVisible, false);
            const savedPosition = GM_getValue(CONFIG.STORAGE_KEYS.arrowPosition, null);
            if (savedPosition) arrowPosition = JSON.parse(savedPosition);
            
            if (folders.length === 0) createDefaultFolder();
            if (activeFolderId && !folders.find(f => f.id === activeFolderId)) {
                activeFolderId = folders[0]?.id || null;
                saveActiveFolder();
            }
            
            // При загрузке сразу начинаем предзагружать активную папку
            if (activeFolderId) {
                setTimeout(() => prefetchAllFolderWaypoints(activeFolderId), 1000);
            }
        } catch (e) {
            console.error('Ошибка загрузки:', e);
            folders = [];
            createDefaultFolder();
        }
    }

    function createDefaultFolder() {
        const defaultFolder = {
            id: Date.now(),
            name: 'Основная',
            description: 'Основная папка',
            icon: '📁',
            color: '#667eea',
            createdAt: new Date().toISOString(),
            waypoints: [],
            hotkeyMode: 'local'
        };
        folders.push(defaultFolder);
        activeFolderId = defaultFolder.id;
        saveFolders();
        saveActiveFolder();
    }

    function saveFolders() {
        GM_setValue(CONFIG.STORAGE_KEYS.folders, JSON.stringify(folders));
    }

    function saveActiveFolder() {
        GM_setValue(CONFIG.STORAGE_KEYS.activeFolderId, activeFolderId);
    }

    // ========== СОЗДАНИЕ ТОЧЕК ==========
    function createWaypointInFolder(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;
        
        if (folder.waypoints.length >= CONFIG.MAX_WAYPOINTS_PER_FOLDER) {
            showNotification(`❌ Лимит: ${CONFIG.MAX_WAYPOINTS_PER_FOLDER} точек`, '#ff4444');
            return;
        }

        const domain = getCurrentDomain();
        const pageTitle = getCurrentPageTitle();
        const url = window.location.href;

        const existing = folder.waypoints.find(w => w.url === url);
        if (existing) {
            showNotification('⚠️ Уже есть в папке', '#ffa500');
            return;
        }

        const newWaypoint = {
            id: Date.now(),
            domain: domain,
            title: pageTitle,
            url: url,
            hotkey: null,
            createdAt: new Date().toISOString(),
            lastUsed: null,
            useCount: 0
        };

        folder.waypoints.push(newWaypoint);
        saveFolders();
        renderWaypointsView();
        showNotification(`✅ "${pageTitle}" добавлена`, '#00ff88');
        
        // Предзагружаем новую точку
        instantPrefetch(url);
    }

    function deleteWaypointFromFolder(folderId, waypointId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;
        
        const waypoint = folder.waypoints.find(w => w.id === waypointId);
        if (waypoint && confirm(`Удалить "${waypoint.title}"?`)) {
            folder.waypoints = folder.waypoints.filter(w => w.id !== waypointId);
            saveFolders();
            renderWaypointsView();
            showNotification('🗑️ Удалено', '#ff4444');
        }
    }

    // ========== ГОРЯЧИЕ КЛАВИШИ (МГНОВЕННЫЙ ПЕРЕХОД) ==========
    function startKeyCapture(folderId, waypointId) {
        if (isCapturingKey) {
            showNotification('⚠️ Уже идет захват', '#ffa500');
            return;
        }

        isCapturingKey = true;
        currentCaptureId = { folderId, waypointId };

        const indicator = createCaptureIndicator();
        document.body.appendChild(indicator);

        captureTimeout = setTimeout(() => {
            if (isCapturingKey) {
                stopKeyCapture();
                showNotification('❌ Время вышло', '#ff4444');
            }
        }, CONFIG.CAPTURE_TIMEOUT);

        const captureHandler = (e) => {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
            if (e.key === 'Escape') {
                stopKeyCapture();
                showNotification('❌ Отменено', '#ff4444');
                return;
            }
            if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') return;

            e.preventDefault();
            e.stopPropagation();

            const hotkey = {
                ctrl: e.ctrlKey,
                alt: e.altKey,
                shift: e.shiftKey,
                meta: e.metaKey,
                key: e.key
            };

            const folder = folders.find(f => f.id === folderId);
            const waypoint = folder?.waypoints.find(w => w.id === waypointId);
            if (waypoint) {
                waypoint.hotkey = hotkey;
                saveFolders();
                renderWaypointsView();
                showNotification(`✅ Назначено: ${formatHotkey(hotkey)}`, '#00ff88');
            }

            stopKeyCapture();
        };

        window.addEventListener('keydown', captureHandler, true);
        window.__captureHandler = captureHandler;
    }

    function stopKeyCapture() {
        if (captureTimeout) clearTimeout(captureTimeout);
        if (window.__captureHandler) {
            window.removeEventListener('keydown', window.__captureHandler, true);
            window.__captureHandler = null;
        }
        const indicator = document.getElementById('key-capture-indicator');
        if (indicator) indicator.remove();
        isCapturingKey = false;
        currentCaptureId = null;
    }

    // ========== МГНОВЕННАЯ ПРОВЕРКА КЛАВИШ ==========
    function checkHotkey(e) {
        if (isCapturingKey) return;

        const target = e.target;
        if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;
        if (e.key === 'Control' || e.key === 'Alt' || e.key === 'Shift' || e.key === 'Meta') return;

        const pressedHotkey = {
            ctrl: e.ctrlKey,
            alt: e.altKey,
            shift: e.shiftKey,
            meta: e.metaKey,
            key: e.key
        };

        const currentDomain = getCurrentDomain();
        const activeFolder = folders.find(f => f.id === activeFolderId);
        
        if (activeFolder) {
            const waypoint = activeFolder.waypoints.find(w => {
                if (!w.hotkey) return false;
                const hotkeyMatch = w.hotkey.ctrl === pressedHotkey.ctrl &&
                                   w.hotkey.alt === pressedHotkey.alt &&
                                   w.hotkey.shift === pressedHotkey.shift &&
                                   w.hotkey.meta === pressedHotkey.meta &&
                                   w.hotkey.key === pressedHotkey.key;
                if (!hotkeyMatch) return false;
                if (activeFolder.hotkeyMode === 'global') return true;
                return w.domain === currentDomain;
            });

            if (waypoint) {
                e.preventDefault();
                e.stopPropagation();
                
                waypoint.lastUsed = new Date().toISOString();
                waypoint.useCount++;
                saveFolders();
                
                // МГНОВЕННЫЙ ПЕРЕХОД (страница уже предзагружена!)
                showNotification(`⚡ МГНОВЕННО: ${waypoint.title}`, '#00ff88');
                window.location.href = waypoint.url;
            }
        }
    }

    // ========== ЭКСПОРТ/ИМПОРТ ==========
    function exportFolder(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;
        
        const exportData = {
            name: folder.name,
            description: folder.description,
            icon: folder.icon,
            color: folder.color,
            hotkeyMode: folder.hotkeyMode,
            waypoints: folder.waypoints,
            exportedAt: new Date().toISOString()
        };
        
        const data = JSON.stringify(exportData, null, 2);
        const blob = new Blob([data], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `folder_${folder.name}_${new Date().toISOString().slice(0,19)}.json`;
        a.click();
        URL.revokeObjectURL(url);
        showNotification(`📁 "${folder.name}" экспортирована`, '#00ff88');
    }

    function importFolder() {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = '.json';
        fileInput.onchange = (e) => {
            const file = e.target.files[0];
            if (!file) return;
            
            const reader = new FileReader();
            reader.onload = (event) => {
                try {
                    const imported = JSON.parse(event.target.result);
                    const newFolder = {
                        id: Date.now(),
                        name: imported.name + ' (импорт)',
                        description: imported.description || '',
                        icon: imported.icon || '📁',
                        color: imported.color || '#667eea',
                        createdAt: new Date().toISOString(),
                        waypoints: imported.waypoints || [],
                        hotkeyMode: imported.hotkeyMode || 'local'
                    };
                    folders.push(newFolder);
                    saveFolders();
                    renderFoldersView();
                    showNotification(`📥 "${newFolder.name}" импортирована`, '#00ff88');
                    
                    // Предзагружаем импортированные точки
                    setTimeout(() => prefetchAllFolderWaypoints(newFolder.id), 500);
                } catch (err) {
                    showNotification('❌ Ошибка импорта', '#ff4444');
                }
            };
            reader.readAsText(file);
        };
        fileInput.click();
    }

    // ========== ОТРИСОВКА ИНТЕРФЕЙСА ==========
    function renderFoldersView() {
        const container = document.getElementById('main-container');
        if (!container) return;

        if (folders.length === 0) {
            container.innerHTML = `<div class="empty-state"><div class="empty-icon">📁</div><div>Нет папок</div><small>Нажмите "Создать папку"</small></div>`;
            return;
        }

        container.innerHTML = `
            <div class="folders-grid">
                ${folders.map(folder => `
                    <div class="folder-card ${activeFolderId === folder.id ? 'active' : ''}" style="border-color: ${folder.color}">
                        <div class="folder-header">
                            <div class="folder-icon" style="background: ${folder.color}20"><span>${folder.icon}</span></div>
                            <div class="folder-actions">
                                <button class="folder-action-btn select-folder" data-id="${folder.id}" title="Сделать активной (мгновенная предзагрузка)">
                                    ${activeFolderId === folder.id ? '✅' : '○'}
                                </button>
                                <button class="folder-action-btn export-folder" data-id="${folder.id}" title="Экспорт">💾</button>
                                <button class="folder-action-btn delete-folder" data-id="${folder.id}" title="Удалить">🗑️</button>
                            </div>
                        </div>
                        <div class="folder-body">
                            <h3 class="folder-name">${escapeHtml(folder.name)}</h3>
                            <div class="folder-stats">
                                <span>📌 ${folder.waypoints.length} точек</span>
                                <span class="mode-badge" style="background: ${folder.hotkeyMode === 'global' ? '#00ff8820' : '#ffa50020'}">
                                    ${folder.hotkeyMode === 'global' ? '🌍 Глобальный' : '📍 Локальный'}
                                </span>
                            </div>
                            ${activeFolderId === folder.id ? '<div class="active-badge">✅ АКТИВНА (предзагружено)</div>' : ''}
                        </div>
                        <div class="folder-footer">
                            <button class="folder-btn open-folder" data-id="${folder.id}">📂 Открыть (предзагрузка)</button>
                        </div>
                    </div>
                `).join('')}
            </div>
        `;

        document.querySelectorAll('.open-folder').forEach(btn => {
            btn.onclick = () => openFolderAndPrefetch(parseInt(btn.dataset.id));
        });
        document.querySelectorAll('.select-folder').forEach(btn => {
            btn.onclick = () => setActiveFolderAndPrefetch(parseInt(btn.dataset.id));
        });
        document.querySelectorAll('.export-folder').forEach(btn => {
            btn.onclick = () => exportFolder(parseInt(btn.dataset.id));
        });
        document.querySelectorAll('.delete-folder').forEach(btn => {
            btn.onclick = () => deleteFolder(parseInt(btn.dataset.id));
        });
    }

    function renderWaypointsView() {
        const container = document.getElementById('main-container');
        const folder = folders.find(f => f.id === currentFolderId);
        
        if (!folder) {
            backToFolders();
            return;
        }

        const searchTerm = document.getElementById('search-input')?.value?.toLowerCase() || '';
        let waypoints = [...folder.waypoints];
        if (searchTerm) {
            waypoints = waypoints.filter(w => w.title.toLowerCase().includes(searchTerm) || w.domain.toLowerCase().includes(searchTerm));
        }

        container.innerHTML = `
            <div class="waypoints-header">
                <button class="back-btn" id="back-to-folders">← Назад к папкам</button>
                <div class="folder-info">
                    <span class="folder-icon-large">${folder.icon}</span>
                    <div>
                        <h2>${escapeHtml(folder.name)}</h2>
                        <div class="folder-controls">
                            <button class="mode-toggle-btn" id="toggle-folder-mode">
                                ${folder.hotkeyMode === 'global' ? '🌍 Глобальный режим' : '📍 Локальный режим'}
                            </button>
                            <button class="save-page-btn" id="save-current-page">➕ Сохранить эту страницу</button>
                        </div>
                    </div>
                </div>
            </div>
            <div class="search-bar"><input type="text" id="search-input" placeholder="🔍 Поиск..." class="search-input"></div>
            <div class="prefetch-info">⚡ Все точки в этой папке предзагружаются фоном для мгновенного перехода!</div>
            <div class="waypoints-list">
                ${waypoints.length === 0 ? `
                    <div class="empty-state"><div class="empty-icon">📍</div><div>Нет точек</div><small>Нажмите "Сохранить"</small></div>
                ` : waypoints.map(wp => `
                    <div class="waypoint-card">
                        <div class="waypoint-info">
                            <div class="waypoint-title">📌 ${escapeHtml(wp.title.substring(0, 50))}</div>
                            <div class="waypoint-meta">
                                ${wp.hotkey ? `<span class="hotkey-badge">⌨️ ${formatHotkey(wp.hotkey)}</span>` : '<span class="no-hotkey">⚡ нет клавиши</span>'}
                                <span class="domain-badge">🌐 ${wp.domain}</span>
                                ${wp.useCount ? `<span class="use-count">📊 ${wp.useCount}</span>` : ''}
                            </div>
                        </div>
                        <div class="waypoint-actions">
                            <button class="action-btn hotkey-btn" data-id="${wp.id}" title="Назначить клавишу">⌨️</button>
                            <button class="action-btn goto-btn" data-url="${wp.url}" title="Перейти (мгновенно)">⚡🔗</button>
                            <button class="action-btn delete-btn" data-id="${wp.id}" title="Удалить">✖</button>
                        </div>
                    </div>
                `).join('')}
            </div>
        `;

        document.getElementById('back-to-folders').onclick = backToFolders;
        document.getElementById('save-current-page').onclick = () => createWaypointInFolder(currentFolderId);
        document.getElementById('toggle-folder-mode').onclick = () => toggleFolderHotkeyMode(currentFolderId);
        
        const searchInput = document.getElementById('search-input');
        if (searchInput) searchInput.oninput = () => renderWaypointsView();

        document.querySelectorAll('.goto-btn').forEach(btn => {
            btn.onclick = () => { window.location.href = btn.dataset.url; };
        });
        document.querySelectorAll('.delete-btn').forEach(btn => {
            btn.onclick = () => deleteWaypointFromFolder(currentFolderId, parseInt(btn.dataset.id));
        });
        document.querySelectorAll('.hotkey-btn').forEach(btn => {
            btn.onclick = () => startKeyCapture(currentFolderId, parseInt(btn.dataset.id));
        });
    }

    function toggleFolderHotkeyMode(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (folder) {
            folder.hotkeyMode = folder.hotkeyMode === 'global' ? 'local' : 'global';
            saveFolders();
            renderWaypointsView();
            showNotification(`Режим: ${folder.hotkeyMode === 'global' ? 'Глобальный' : 'Локальный'}`, '#ffa500');
        }
    }

    function deleteFolder(folderId) {
        const folder = folders.find(f => f.id === folderId);
        if (!folder) return;
        if (confirm(`Удалить папку "${folder.name}" (${folder.waypoints.length} точек)?`)) {
            folders = folders.filter(f => f.id !== folderId);
            if (activeFolderId === folderId) {
                activeFolderId = folders[0]?.id || null;
                saveActiveFolder();
            }
            saveFolders();
            if (currentView === 'waypoints' && currentFolderId === folderId) backToFolders();
            renderFoldersView();
            showNotification(`🗑️ Удалено`, '#ff4444');
        }
    }

    function backToFolders() {
        currentView = 'folders';
        currentFolderId = null;
        renderFoldersView();
    }

    function renderMainInterface() {
        const panel = document.getElementById('hotkeys-main-panel');
        if (!panel) return;
        const content = panel.querySelector('.panel-content');
        if (!content) return;

        content.innerHTML = `
            <div class="interface-header">
                <div class="logo"><span class="logo-icon">⚡</span><span class="logo-text">INSTANT WAYPOINTS</span></div>
                <div class="header-buttons">
                    <button id="create-folder-btn" class="primary-btn">➕ Создать папку</button>
                    <button id="import-folder-btn" class="secondary-btn">📂 Импорт</button>
                </div>
            </div>
            <div id="main-container" class="main-container"></div>
        `;

        document.getElementById('create-folder-btn').onclick = createFolder;
        document.getElementById('import-folder-btn').onclick = importFolder;
        
        if (currentView === 'folders') renderFoldersView();
        else renderWaypointsView();
    }

    function createFolder() {
        if (folders.length >= CONFIG.MAX_FOLDERS) {
            showNotification(`❌ Лимит: ${CONFIG.MAX_FOLDERS} папок`, '#ff4444');
            return;
        }
        const name = prompt('Название папки:', 'Новая папка');
        if (!name) return;

        const newFolder = {
            id: Date.now(),
            name: name.substring(0, 30),
            description: '',
            icon: '📁',
            color: '#667eea',
            createdAt: new Date().toISOString(),
            waypoints: [],
            hotkeyMode: 'local'
        };
        folders.push(newFolder);
        saveFolders();
        renderFoldersView();
        showNotification(`✅ "${name}" создана`, '#00ff88');
    }

    function createCaptureIndicator() {
        const div = document.createElement('div');
        div.id = 'key-capture-indicator';
        div.innerHTML = `<div class="capture-content"><div class="capture-icon">⌨️</div><div class="capture-text">Нажмите любую комбинацию...</div><div class="capture-hint">Esc - отмена</div><div class="capture-pulse"></div></div>`;
        return div;
    }

    // ========== УПРАВЛЕНИЕ ПАНЕЛЬЮ ==========
    function togglePanel() {
        panelVisible = !panelVisible;
        GM_setValue(CONFIG.STORAGE_KEYS.panelVisible, panelVisible);
        const panel = document.getElementById('hotkeys-main-panel');
        const arrow = document.getElementById('nav-arrow-trigger');
        if (panelVisible) {
            panel.classList.add('panel-visible');
            panel.classList.remove('panel-hidden');
            if (arrow) arrow.classList.add('arrow-active');
        } else {
            panel.classList.remove('panel-visible');
            panel.classList.add('panel-hidden');
            if (arrow) arrow.classList.remove('arrow-active');
        }
    }

    function createArrowTrigger() {
        const existing = document.getElementById('nav-arrow-trigger');
        if (existing) existing.remove();

        const arrow = document.createElement('div');
        arrow.id = 'nav-arrow-trigger';
        arrow.className = 'nav-arrow';
        arrow.innerHTML = `<div class="arrow-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div><div class="arrow-tooltip">Мгновенная навигация</div>`;
        arrow.style.left = `${arrowPosition.x}px`;
        arrow.style.top = `${arrowPosition.y}px`;
        arrow.onclick = (e) => { e.stopPropagation(); togglePanel(); };
        makeArrowDraggable(arrow);
        document.body.appendChild(arrow);
        return arrow;
    }

    function makeArrowDraggable(element) {
        let isDragging = false, startX, startY, startLeft, startTop;
        element.onmousedown = (e) => {
            if (e.target.closest('.arrow-icon') || e.target === element) {
                isDragging = true;
                startX = e.clientX;
                startY = e.clientY;
                startLeft = parseInt(element.style.left);
                startTop = parseInt(element.style.top);
                document.body.style.userSelect = 'none';
                element.style.cursor = 'grabbing';
                document.onmousemove = (moveEvent) => {
                    if (!isDragging) return;
                    const newLeft = startLeft + (moveEvent.clientX - startX);
                    const newTop = startTop + (moveEvent.clientY - startY);
                    element.style.left = `${Math.max(5, Math.min(window.innerWidth - 50, newLeft))}px`;
                    element.style.top = `${Math.max(5, Math.min(window.innerHeight - 50, newTop))}px`;
                };
                document.onmouseup = () => {
                    isDragging = false;
                    document.body.style.userSelect = '';
                    element.style.cursor = '';
                    document.onmousemove = null;
                    document.onmouseup = null;
                    arrowPosition = { x: parseInt(element.style.left), y: parseInt(element.style.top) };
                    GM_setValue(CONFIG.STORAGE_KEYS.arrowPosition, JSON.stringify(arrowPosition));
                };
                e.preventDefault();
            }
        };
    }

    function createMainPanel() {
        const existing = document.getElementById('hotkeys-main-panel');
        if (existing) existing.remove();

        const panel = document.createElement('div');
        panel.id = 'hotkeys-main-panel';
        panel.className = panelVisible ? 'panel-visible' : 'panel-hidden';
        panel.innerHTML = `<div class="panel-header"><div class="header-title"><span class="neon-text">⚡ INSTANT NAVIGATION</span></div><div class="header-actions"><button class="icon-btn close-btn" id="close-panel-btn">✖</button></div></div><div class="panel-content"></div>`;
        document.body.appendChild(panel);
        document.getElementById('close-panel-btn').onclick = togglePanel;
        renderMainInterface();
    }

    function showNotification(msg, color) {
        const notif = document.createElement('div');
        notif.textContent = msg;
        notif.style.cssText = `position:fixed;bottom:30px;right:30px;background:linear-gradient(135deg,${color},${color}dd);color:white;padding:12px 24px;border-radius:12px;z-index:1000000;font-size:14px;font-weight:bold;box-shadow:0 4px 20px rgba(0,0,0,0.4);animation:cyberNotify 2s ease forwards;pointer-events:none`;
        document.body.appendChild(notif);
        setTimeout(() => notif.remove(), 2000);
    }

    function escapeHtml(str) {
        if (!str) return '';
        return str.replace(/[&<>]/g, m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[m]));
    }

    // ========== СТИЛИ ==========
    GM_addStyle(`
        @keyframes slideInRight { from { opacity: 0; transform: translateX(-100%); } to { opacity: 1; transform: translateX(0); } }
        @keyframes slideOutLeft { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(-100%); visibility: hidden; } }
        @keyframes cyberNotify { 0% { opacity: 0; transform: translateX(100px); } 15% { opacity: 1; transform: translateX(0); } 85% { opacity: 1; transform: translateX(0); } 100% { opacity: 0; transform: translateX(100px); } }
        @keyframes capturePulse { 0% { transform: scale(1); opacity: 0.6; } 100% { transform: scale(2); opacity: 0; } }
        
        .nav-arrow { position: fixed; width: 48px; height: 48px; background: linear-gradient(135deg, #667eea, #764ba2); border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 999998; box-shadow: 0 4px 15px rgba(0,0,0,0.3); transition: all 0.3s; border: 2px solid rgba(255,255,255,0.3); }
        .nav-arrow:hover { transform: scale(1.1); }
        .nav-arrow.arrow-active { background: linear-gradient(135deg, #f093fb, #f5576c); animation: pulse 2s infinite; }
        @keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }
        .arrow-icon svg { width: 28px; height: 28px; color: white; }
        .arrow-tooltip { position: absolute; left: 55px; background: rgba(0,0,0,0.8); color: white; padding: 5px 10px; border-radius: 8px; font-size: 11px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.3s; }
        .nav-arrow:hover .arrow-tooltip { opacity: 1; }
        
        #hotkeys-main-panel { position: fixed; top: 80px; left: 20px; width: 520px; max-width: 90vw; height: 70vh; max-height: 650px; background: linear-gradient(135deg, rgba(15,25,45,0.98), rgba(10,20,35,0.98)); backdrop-filter: blur(20px); border-radius: 20px; border: 1px solid rgba(102,126,234,0.3); z-index: 999997; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; }
        #hotkeys-main-panel.panel-visible { animation: slideInRight 0.3s ease forwards; }
        #hotkeys-main-panel.panel-hidden { animation: slideOutLeft 0.3s ease forwards; display: none; }
        .panel-header { background: linear-gradient(135deg, rgba(102,126,234,0.2), rgba(118,75,162,0.2)); padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .header-title .neon-text { font-size: 16px; font-weight: bold; background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
        .icon-btn { background: rgba(255,255,255,0.1); border: none; padding: 6px 10px; border-radius: 10px; color: white; cursor: pointer; transition: all 0.2s; }
        .icon-btn:hover { background: rgba(255,255,255,0.2); transform: scale(1.05); }
        .panel-content { flex: 1; overflow-y: auto; padding: 20px; }
        .panel-content::-webkit-scrollbar { width: 6px; }
        .panel-content::-webkit-scrollbar-track { background: rgba(255,255,255,0.05); border-radius: 10px; }
        .panel-content::-webkit-scrollbar-thumb { background: #667eea; border-radius: 10px; }
        
        .interface-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .logo { display: flex; align-items: center; gap: 10px; }
        .logo-icon { font-size: 28px; }
        .logo-text { font-size: 18px; font-weight: bold; background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
        .header-buttons { display: flex; gap: 10px; }
        .primary-btn, .secondary-btn { padding: 8px 16px; border: none; border-radius: 10px; cursor: pointer; font-weight: 500; transition: all 0.2s; }
        .primary-btn { background: linear-gradient(135deg, #667eea, #764ba2); color: white; }
        .primary-btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102,126,234,0.4); }
        .secondary-btn { background: rgba(255,255,255,0.1); color: white; border: 1px solid rgba(255,255,255,0.2); }
        .secondary-btn:hover { background: rgba(255,255,255,0.2); }
        
        .folders-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
        .folder-card { background: rgba(255,255,255,0.05); border-radius: 16px; border-left: 4px solid; padding: 16px; transition: all 0.3s; cursor: pointer; }
        .folder-card:hover { transform: translateY(-2px); background: rgba(255,255,255,0.08); }
        .folder-card.active { background: linear-gradient(135deg, rgba(102,126,234,0.15), rgba(118,75,162,0.15)); border-left-width: 6px; }
        .folder-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
        .folder-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 28px; }
        .folder-actions { display: flex; gap: 6px; }
        .folder-action-btn { background: rgba(255,255,255,0.1); border: none; padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; transition: all 0.2s; }
        .folder-action-btn:hover { background: rgba(255,255,255,0.2); transform: scale(1.05); }
        .folder-name { color: white; font-size: 16px; font-weight: 600; margin: 0 0 8px 0; }
        .folder-stats { display: flex; gap: 12px; font-size: 12px; color: rgba(255,255,255,0.6); }
        .mode-badge { padding: 2px 8px; border-radius: 12px; font-size: 10px; }
        .active-badge { margin-top: 8px; font-size: 10px; color: #00ff88; background: rgba(0,255,136,0.1); padding: 2px 8px; border-radius: 12px; display: inline-block; }
        .folder-footer { margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.1); }
        .folder-btn { width: 100%; padding: 8px; background: rgba(255,255,255,0.1); border: none; border-radius: 8px; color: white; cursor: pointer; transition: all 0.2s; }
        .folder-btn:hover { background: rgba(255,255,255,0.2); }
        
        .waypoints-header { margin-bottom: 20px; }
        .back-btn { background: rgba(255,255,255,0.1); border: none; padding: 8px 16px; border-radius: 10px; color: white; cursor: pointer; margin-bottom: 16px; transition: all 0.2s; }
        .back-btn:hover { background: rgba(255,255,255,0.2); }
        .folder-info { display: flex; align-items: center; gap: 16px; }
        .folder-icon-large { font-size: 48px; }
        .folder-info h2 { color: white; margin: 0 0 8px 0; }
        .folder-controls { display: flex; gap: 10px; }
        .mode-toggle-btn, .save-page-btn { padding: 6px 12px; border: none; border-radius: 8px; cursor: pointer; font-size: 12px; transition: all 0.2s; }
        .mode-toggle-btn { background: rgba(255,165,0,0.2); color: #ffa500; }
        .save-page-btn { background: linear-gradient(135deg, #00ff88, #00bfff); color: #0a0a0a; font-weight: bold; }
        .search-bar { margin-bottom: 20px; }
        .search-input { width: 100%; padding: 10px 14px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 12px; color: white; font-size: 14px; box-sizing: border-box; }
        .search-input:focus { outline: none; border-color: #667eea; }
        .prefetch-info { background: rgba(0,255,136,0.1); border: 1px solid #00ff88; border-radius: 10px; padding: 8px 12px; margin-bottom: 16px; font-size: 11px; color: #00ff88; text-align: center; }
        .waypoints-list { display: flex; flex-direction: column; gap: 10px; }
        .waypoint-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 12px; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s; }
        .waypoint-card:hover { background: rgba(255,255,255,0.08); transform: translateX(4px); }
        .waypoint-info { flex: 1; }
        .waypoint-title { color: white; font-size: 14px; font-weight: 500; margin-bottom: 6px; }
        .waypoint-meta { display: flex; gap: 8px; flex-wrap: wrap; }
        .hotkey-badge, .no-hotkey, .domain-badge, .use-count { padding: 2px 8px; border-radius: 12px; font-size: 10px; }
        .hotkey-badge { background: rgba(0,255,136,0.15); color: #00ff88; font-family: monospace; }
        .no-hotkey { background: rgba(255,100,100,0.15); color: #ff6464; }
        .domain-badge { background: rgba(102,126,234,0.15); color: #667eea; }
        .use-count { background: rgba(0,191,255,0.15); color: #00bfff; }
        .waypoint-actions { display: flex; gap: 6px; }
        .action-btn { background: rgba(255,255,255,0.1); border: none; padding: 6px 10px; border-radius: 8px; cursor: pointer; transition: all 0.2s; }
        .action-btn:hover { transform: scale(1.05); }
        .hotkey-btn:hover { background: #3b82f6; color: white; }
        .goto-btn:hover { background: #22c55e; color: white; }
        .delete-btn:hover { background: #dc2626; color: white; }
        .empty-state { text-align: center; padding: 60px 20px; color: rgba(255,255,255,0.4); }
        .empty-icon { font-size: 64px; margin-bottom: 16px; }
        
        #key-capture-indicator { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1000000; }
        .capture-content { background: linear-gradient(135deg, #1a1a2e, #16213e); border: 2px solid #667eea; border-radius: 24px; padding: 30px 50px; text-align: center; box-shadow: 0 0 40px rgba(102,126,234,0.5); position: relative; }
        .capture-icon { font-size: 48px; margin-bottom: 15px; }
        .capture-text { color: #667eea; font-size: 18px; font-weight: bold; margin-bottom: 10px; }
        .capture-hint { color: #888; font-size: 12px; margin: 5px 0; }
        .capture-pulse { position: absolute; top: 50%; left: 50%; width: 100%; height: 100%; border: 2px solid #667eea; border-radius: 24px; transform: translate(-50%, -50%); animation: capturePulse 1.5s infinite; pointer-events: none; }
    `);

    // ========== ЗАПУСК ==========
    function init() {
        loadData();
        createArrowTrigger();
        createMainPanel();
        document.addEventListener('keydown', checkHotkey);
        console.log('%c⚡ INSTANT WAYPOINTS PRO - МГНОВЕННАЯ НАВИГАЦИЯ ⚡', 'color: #00ff88; font-size: 16px; font-weight: bold;');
        console.log('%c🚀 Предзагрузка активна! При нажатии клавиши переход будет МГНОВЕННЫМ!', 'color: #ffa500;');
    }

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