ASSSQuick Nav

Добавление кнопок быстрого доступа

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         ASSSQuick Nav
// @version      3.0
// @description  Добавление кнопок быстрого доступа
// @match        *://animesss.com/*
// @match        *://animesss.tv/*
// @author       SoulUA
// @license      MIT
// @grant        none
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================
    // 1. КОНСТАНТЫ И ДЕФОЛТНЫЕ НАСТРОЙКИ
    // ==========================================
    const STORAGE_KEY_LINKS = 'animesss_quick_nav_links';
    const STORAGE_KEY_SETTINGS = 'animesss_quick_nav_settings';
    const TARGET_SELECTOR = 'header';

    const DEFAULT_LINKS = [
        { title: 'Лента карт', url: '/cards' },
        { title: 'Паки карт', url: '/cards/pack/' },
        { title: 'Трейды', url: '/trades/offers/' }
    ];

    const DEFAULT_SETTINGS = {
        bgColor: '#121212',
        bgOpacity: 0,
        blur: 0,
        btnBgColor: '#212121',
        btnTextColor: '#e0e0e0',
        fontFamily: 'system-ui, sans-serif',
        btnFontSize: 13,
        btnPadY: 10,
        btnPadX: 16
    };

    let resizeObserver = null;
    let isScrollListenerAttached = false;

    // ==========================================
    // 2. УПРАВЛЕНИЕ ДАННЫМИ
    // ==========================================
    function getLinks() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY_LINKS);
            return stored ? JSON.parse(stored) : DEFAULT_LINKS;
        } catch (e) {
            return DEFAULT_LINKS;
        }
    }

    function saveLinks(links) {
        localStorage.setItem(STORAGE_KEY_LINKS, JSON.stringify(links));
    }

    function getSettings() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY_SETTINGS);
            return stored ? { ...DEFAULT_SETTINGS, ...JSON.parse(stored) } : DEFAULT_SETTINGS;
        } catch (e) {
            return DEFAULT_SETTINGS;
        }
    }

    function saveSettings(settings) {
        localStorage.setItem(STORAGE_KEY_SETTINGS, JSON.stringify(settings));
    }

    function hexToRgb(hex) {
        let c;
        if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
            c = hex.substring(1).split('');
            if (c.length === 3) c = [c[0], c[0], c[1], c[1], c[2], c[2]];
            c = '0x' + c.join('');
            return [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',');
        }
        return '18,18,18';
    }

    // ==========================================
    // 3. РЕНДЕР И СТИЛИЗАЦИЯ
    // ==========================================
    function applyVisualSettings() {
        const navContainer = document.getElementById('custom-quick-nav');
        if (!navContainer) return;

        const settings = getSettings();
        const rgb = hexToRgb(settings.bgColor);

        navContainer.style.backgroundColor = `rgba(${rgb}, ${settings.bgOpacity})`;
        navContainer.style.backdropFilter = `blur(${settings.blur}px)`;
        navContainer.style.webkitBackdropFilter = `blur(${settings.blur}px)`;

        navContainer.style.setProperty('--btn-bg', settings.btnBgColor || '#212121');
        navContainer.style.setProperty('--btn-text', settings.btnTextColor || '#e0e0e0');
        navContainer.style.setProperty('--btn-font', settings.fontFamily || 'system-ui, sans-serif');
        navContainer.style.setProperty('--btn-fs', `${settings.btnFontSize || 13}px`);
        navContainer.style.setProperty('--btn-pad', `${settings.btnPadY || 10}px ${settings.btnPadX || 16}px`);
    }

    function renderButtons() {
        const navContainer = document.getElementById('custom-quick-nav');
        if (!navContainer) return;

        navContainer.innerHTML = '';
        const links = getLinks();
        const fragment = document.createDocumentFragment();

        links.forEach(link => {
            const btn = document.createElement('a');
            btn.href = link.url;
            btn.textContent = link.title;
            btn.className = 'custom-nav-btn';
            fragment.appendChild(btn);
        });

        const settingsBtn = document.createElement('button');
        settingsBtn.textContent = '⚙️';
        settingsBtn.className = 'custom-nav-btn settings-btn';
        settingsBtn.title = 'Настройки Quick Nav';
        settingsBtn.onclick = openSettingsModal;
        fragment.appendChild(settingsBtn);

        navContainer.appendChild(fragment);
    }

    function injectNavigation() {
        if (document.getElementById('custom-quick-nav')) return;

        const targetElement = document.querySelector(TARGET_SELECTOR);
        if (!targetElement) return;

        injectStyles();

        const spacer = document.createElement('div');
        spacer.id = 'aqn-spacer';

        const navContainer = document.createElement('div');
        navContainer.id = 'custom-quick-nav';

        targetElement.insertAdjacentElement('afterend', spacer);
        targetElement.insertAdjacentElement('afterend', navContainer);

        renderButtons();
        applyVisualSettings();
        createSettingsModal();
        setupPositioningTracking(targetElement, navContainer, spacer);
    }

    function setupPositioningTracking(targetElement, navContainer, spacer) {
        if (resizeObserver) resizeObserver.disconnect();

        const updateStickyPosition = () => {
            if (!document.body.contains(targetElement) || !document.body.contains(navContainer)) return;
            const headerRect = targetElement.getBoundingClientRect();

            navContainer.style.top = `${headerRect.bottom}px`;
            navContainer.style.left = `${headerRect.left}px`;
            navContainer.style.width = `${headerRect.width}px`;

            if (spacer) spacer.style.height = `${navContainer.offsetHeight}px`;
        };

        updateStickyPosition();

        if (window.ResizeObserver) {
            resizeObserver = new ResizeObserver(() => requestAnimationFrame(updateStickyPosition));
            resizeObserver.observe(targetElement);
            resizeObserver.observe(navContainer);
        }

        if (!isScrollListenerAttached) {
            let ticking = false;
            window.addEventListener('scroll', () => {
                if (!ticking) {
                    window.requestAnimationFrame(() => {
                        updateStickyPosition();
                        ticking = false;
                    });
                    ticking = true;
                }
            }, { passive: true });
            isScrollListenerAttached = true;
        }
    }

    // ==========================================
    // 4. UI НАСТРОЕК (Модальное окно)
    // ==========================================
    function createSettingsModal() {
        if (document.getElementById('aqn-modal-overlay')) return;

        const overlay = document.createElement('div');
        overlay.id = 'aqn-modal-overlay';
        overlay.style.display = 'none';

        overlay.innerHTML = `
            <div id="aqn-modal">
                <h3>Настройки Quick Nav</h3>

                <div class="aqn-modal-scroll-area">
                    <fieldset class="aqn-group">
                        <legend>🖥️ Панель</legend>
                        <div class="aqn-setting-row">
                            <label title="Цвет подложки">Цвет: <input type="color" id="aqn-opt-color"></label>
                            <label title="0 - прозрачный, 1 - сплошной">Непрозрачность:
                                <input type="range" id="aqn-opt-opacity" min="0" max="1" step="0.05">
                            </label>
                        </div>
                        <div class="aqn-setting-row">
                            <label title="Эффект матового стекла (размытие под панелью)">Размытие фона (px):
                                <input type="range" id="aqn-opt-blur" min="0" max="20" step="1">
                                <span id="aqn-blur-val" class="val-span"></span>
                            </label>
                        </div>
                    </fieldset>

                    <fieldset class="aqn-group">
                        <legend>🎨 Кнопки</legend>
                        <div class="aqn-setting-row">
                            <label>Фон: <input type="color" id="aqn-opt-btn-bg"></label>
                            <label>Текст: <input type="color" id="aqn-opt-btn-text"></label>
                        </div>
                        <div class="aqn-setting-row">
                            <label title="Шрифт текста">Шрифт:
                                <select id="aqn-opt-font" class="aqn-select">
                                    <option value="system-ui, sans-serif">Системный (По умолчанию)</option>
                                    <option value="Georgia, serif">Georgia (Элегантный)</option>
                                    <option value="'Palatino Linotype', 'Book Antiqua', serif">Palatino (Книжный)</option>
                                    <option value="'Trebuchet MS', sans-serif">Trebuchet MS (Компактный)</option>
                                    <option value="Impact, sans-serif">Impact (Массивный)</option>
                                    <option value="'Arial Black', sans-serif">Arial Black (Жирный)</option>
                                    <option value="'Comic Sans MS', cursive">Comic Sans (Неформальный)</option>
                                    <option value="'Courier New', monospace">Courier New (Печатная машинка)</option>
                                    <option value="'Lucida Console', monospace">Lucida Console (Терминал)</option>
                                </select>
                            </label>
                        </div>
                        <div class="aqn-setting-row">
                            <label>Размер шрифта:
                                <input type="range" id="aqn-opt-fs" min="10" max="24" step="1">
                                <span id="aqn-fs-val" class="val-span"></span>
                            </label>
                        </div>
                        <div class="aqn-setting-row">
                            <label>Высота (отступ):
                                <input type="range" id="aqn-opt-pady" min="4" max="24" step="1">
                                <span id="aqn-pady-val" class="val-span"></span>
                            </label>
                            <label>Ширина (отступ):
                                <input type="range" id="aqn-opt-padx" min="4" max="32" step="1">
                                <span id="aqn-padx-val" class="val-span"></span>
                            </label>
                        </div>
                    </fieldset>

                    <fieldset class="aqn-group">
                        <legend>🔗 Ссылки</legend>
                        <div id="aqn-links-list"></div>
                        <button id="aqn-add-link-btn">+ Добавить ссылку</button>
                    </fieldset>
                </div>

                <div class="aqn-modal-actions">
                    <button id="aqn-save-btn">Сохранить</button>
                    <button id="aqn-cancel-btn">Отмена</button>
                    <button id="aqn-reset-btn" title="Сбросить все">Сброс</button>
                </div>
            </div>
        `;

        document.body.appendChild(overlay);

        // Привязка ползунков к значениям
        const bindRange = (inputId, valId) => {
            const input = document.getElementById(inputId);
            const val = document.getElementById(valId);
            input.addEventListener('input', (e) => val.textContent = e.target.value);
        };
        bindRange('aqn-opt-blur', 'aqn-blur-val');
        bindRange('aqn-opt-fs', 'aqn-fs-val');
        bindRange('aqn-opt-pady', 'aqn-pady-val');
        bindRange('aqn-opt-padx', 'aqn-padx-val');

        document.getElementById('aqn-add-link-btn').onclick = () => addLinkRow();
        document.getElementById('aqn-cancel-btn').onclick = closeSettingsModal;
        document.getElementById('aqn-save-btn').onclick = saveSettingsFromModal;
        document.getElementById('aqn-reset-btn').onclick = () => {
            if (confirm('Сбросить все настройки и ссылки по умолчанию?')) {
                saveLinks(DEFAULT_LINKS);
                saveSettings(DEFAULT_SETTINGS);
                renderButtons();
                applyVisualSettings();
                closeSettingsModal();
            }
        };

        overlay.addEventListener('mousedown', (e) => {
            if (e.target === overlay) closeSettingsModal();
        });
    }

    function addLinkRow(title = '', url = '') {
        const list = document.getElementById('aqn-links-list');
        const row = document.createElement('div');
        row.className = 'aqn-link-row';

        row.innerHTML = `
            <input type="text" class="aqn-input-title" placeholder="Название">
            <input type="text" class="aqn-input-url" placeholder="URL (напр. /cards)">
            <button class="aqn-del-btn" title="Удалить">✖</button>
        `;

        row.querySelector('.aqn-input-title').value = title;
        row.querySelector('.aqn-input-url').value = url;
        row.querySelector('.aqn-del-btn').onclick = () => row.remove();

        list.appendChild(row);
    }

    function openSettingsModal() {
        const list = document.getElementById('aqn-links-list');
        list.innerHTML = '';
        const links = getLinks();
        links.forEach(link => addLinkRow(link.title, link.url));

        const settings = getSettings();

        const setVal = (id, val, textId = null) => {
            document.getElementById(id).value = val;
            if (textId) document.getElementById(textId).textContent = val;
        };

        setVal('aqn-opt-color', settings.bgColor);
        setVal('aqn-opt-opacity', settings.bgOpacity);
        setVal('aqn-opt-blur', settings.blur, 'aqn-blur-val');
        setVal('aqn-opt-btn-bg', settings.btnBgColor || '#212121');
        setVal('aqn-opt-btn-text', settings.btnTextColor || '#e0e0e0');
        setVal('aqn-opt-font', settings.fontFamily || 'system-ui, sans-serif');
        setVal('aqn-opt-fs', settings.btnFontSize || 13, 'aqn-fs-val');
        setVal('aqn-opt-pady', settings.btnPadY || 10, 'aqn-pady-val');
        setVal('aqn-opt-padx', settings.btnPadX || 16, 'aqn-padx-val');

        document.getElementById('aqn-modal-overlay').style.display = 'flex';
    }

    function closeSettingsModal() {
        document.getElementById('aqn-modal-overlay').style.display = 'none';
    }

    function saveSettingsFromModal() {
        const rows = document.querySelectorAll('.aqn-link-row');
        const newLinks = [];
        rows.forEach(row => {
            const title = row.querySelector('.aqn-input-title').value.trim();
            const url = row.querySelector('.aqn-input-url').value.trim();
            if (title || url) newLinks.push({ title: title || 'Без названия', url: url || '#' });
        });
        saveLinks(newLinks);

        const newSettings = {
            bgColor: document.getElementById('aqn-opt-color').value,
            bgOpacity: parseFloat(document.getElementById('aqn-opt-opacity').value),
            blur: parseInt(document.getElementById('aqn-opt-blur').value, 10),
            btnBgColor: document.getElementById('aqn-opt-btn-bg').value,
            btnTextColor: document.getElementById('aqn-opt-btn-text').value,
            fontFamily: document.getElementById('aqn-opt-font').value,
            btnFontSize: parseInt(document.getElementById('aqn-opt-fs').value, 10),
            btnPadY: parseInt(document.getElementById('aqn-opt-pady').value, 10),
            btnPadX: parseInt(document.getElementById('aqn-opt-padx').value, 10)
        };
        saveSettings(newSettings);

        renderButtons();
        applyVisualSettings();
        closeSettingsModal();
    }

    // ==========================================
    // 5. CSS СТИЛИ
    // ==========================================
    function injectStyles() {
        if (document.getElementById('aqn-styles')) return;

        const style = document.createElement('style');
        style.id = 'aqn-styles';
        style.textContent = `
            #custom-quick-nav {
                display: flex;
                gap: 8px;
                padding: 12px 16px;
                overflow-x: auto;
                white-space: nowrap;
                scrollbar-width: none;
                -ms-overflow-style: none;
                border-bottom: none;
                box-shadow: none;

                position: fixed;
                box-sizing: border-box;
                z-index: 998;
                justify-content: safe center;
                pointer-events: none;
                transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
            }
            #custom-quick-nav > * { pointer-events: auto; }
            #custom-quick-nav::-webkit-scrollbar { display: none; }
            #aqn-spacer { width: 100%; display: block; flex-shrink: 0; }

            .custom-nav-btn {
                background-color: var(--btn-bg, #212121);
                color: var(--btn-text, #e0e0e0);
                font-family: var(--btn-font, system-ui, sans-serif);
                font-size: var(--btn-fs, 13px);
                padding: var(--btn-pad, 10px 16px);
                text-decoration: none;
                border-radius: 6px;
                font-weight: 600;
                letter-spacing: 0.5px;
                transition: opacity 0.2s ease;
                flex-shrink: 0;
                border: none;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                text-align: center;
                box-shadow: 0 2px 5px rgba(0,0,0,0.5);
            }
            .custom-nav-btn:hover, .custom-nav-btn:active { opacity: 0.8; }

            .settings-btn {
                position: absolute;
                right: 12px;
                top: 50%;
                transform: translateY(-50%);
                background-color: transparent;
                box-shadow: none;
                padding: 8px;
                font-size: 16px;
                opacity: 0.15;
                transition: opacity 0.2s ease, transform 0.2s ease;
                color: var(--btn-text, #e0e0e0);
            }
            .settings-btn:hover, .settings-btn:active {
                background-color: transparent;
                opacity: 1;
                transform: translateY(-50%) scale(1.1);
            }

            /* Модальное окно */
            #aqn-modal-overlay {
                position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
                background: rgba(0, 0, 0, 0.75); z-index: 999999;
                display: flex; align-items: center; justify-content: center; backdrop-filter: blur(3px);
            }
            #aqn-modal {
                background: #1e1e1e; padding: 20px; border-radius: 12px;
                width: 480px; max-width: 90%; max-height: 90vh;
                display: flex; flex-direction: column; gap: 12px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #333; color: #e0e0e0; font-family: sans-serif;
            }
            #aqn-modal h3 { margin: 0; font-size: 18px; color: #fff; border-bottom: 1px solid #333; padding-bottom: 12px; }

            .aqn-modal-scroll-area {
                overflow-y: auto;
                padding-right: 6px;
                display: flex;
                flex-direction: column;
                gap: 16px;
            }
            .aqn-modal-scroll-area::-webkit-scrollbar { width: 6px; }
            .aqn-modal-scroll-area::-webkit-scrollbar-track { background: #121212; border-radius: 4px; }
            .aqn-modal-scroll-area::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }

            /* Группировка (Fieldsets) */
            .aqn-group {
                border: 1px solid #333;
                border-radius: 8px;
                padding: 12px;
                margin: 0;
                background: #151515;
            }
            .aqn-group legend {
                font-weight: bold;
                color: #aaa;
                padding: 0 8px;
                font-size: 14px;
            }

            .aqn-setting-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
            .aqn-setting-row:last-child { margin-bottom: 0; }
            .aqn-setting-row label { display: flex; align-items: center; gap: 8px; cursor: pointer; color: #ccc; font-size: 13px; flex-grow: 1; }
            .aqn-setting-row input[type="color"] { background: none; border: none; width: 28px; height: 28px; cursor: pointer; padding: 0; border-radius: 4px; }
            .aqn-setting-row input[type="range"] { cursor: pointer; flex-grow: 1; max-width: 120px; }
            .aqn-select { background: #222; color: #fff; border: 1px solid #444; padding: 6px; border-radius: 4px; outline: none; width: 100%; max-width: 200px; }
            .val-span { display: inline-block; width: 22px; text-align: right; color: #fff; font-weight: bold; }

            /* Списки ссылок */
            #aqn-links-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
            .aqn-link-row { display: flex; gap: 8px; align-items: center; }
            .aqn-link-row input { background: #222; border: 1px solid #444; color: #fff; padding: 8px 10px; border-radius: 6px; outline: none; transition: border-color 0.2s; font-size: 13px; }
            .aqn-link-row input:focus { border-color: #9d3855; }
            .aqn-input-title { width: 35%; }
            .aqn-input-url { width: 65%; }

            /* Действия / Кнопки */
            .aqn-del-btn, #aqn-add-link-btn, .aqn-modal-actions button { cursor: pointer; border: none; border-radius: 6px; font-weight: 600; transition: background-color 0.2s; font-size: 13px; }
            .aqn-del-btn { background: #3a1a1a; color: #ff5555; padding: 8px 12px; }
            .aqn-del-btn:hover { background: #ff5555; color: #fff; }
            #aqn-add-link-btn { background: #222; color: #aaa; padding: 10px; width: 100%; border: 1px dashed #555; }
            #aqn-add-link-btn:hover { background: #333; color: #fff; }

            .aqn-modal-actions { display: flex; justify-content: space-between; gap: 10px; padding-top: 16px; border-top: 1px solid #333; flex-shrink: 0; }
            #aqn-save-btn { background: #9d3855; color: #fff; padding: 10px 20px; flex-grow: 1; }
            #aqn-save-btn:hover { background: #b84365; }
            #aqn-cancel-btn { background: #333; color: #fff; padding: 10px 20px; }
            #aqn-cancel-btn:hover { background: #444; }
            #aqn-reset-btn { background: transparent; color: #777; padding: 10px; }
            #aqn-reset-btn:hover { color: #ff5555; text-decoration: underline; }
        `;
        document.head.appendChild(style);
    }

    // ==========================================
    // 6. ЗАПУСК И ИНИЦИАЛИЗАЦИЯ
    // ==========================================
    const init = () => {
        injectNavigation();
        const observer = new MutationObserver(() => {
            if (!document.getElementById('custom-quick-nav') && document.querySelector(TARGET_SELECTOR)) {
                injectNavigation();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    };

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