Animesss Quick Nav

Добавление настраиваемых кнопок быстрого доступа под шапку с настройками фона

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

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

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

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

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

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

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

Advertisement:

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

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

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

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

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

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

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

Advertisement:

// ==UserScript==
// @name         Animesss Quick Nav
// @namespace    http://tampermonkey.net/
// @version      2.6
// @description  Добавление настраиваемых кнопок быстрого доступа под шапку с настройками фона
// @match        *://animesss.com/*
// @match        *://animesss.tv/*
// @author       SoulUA
// @grant        none
// ==/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, // 0 = полностью прозрачный
        blur: 0       // 0 = без размытия
    };

    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));
    }

    // Утилита для конвертации HEX в RGB для использования в rgba()
    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)`;
    }

    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-visual-settings">
                    <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"></span>
                        </label>
                    </div>
                </div>

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

                <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);

        // Обновление значения размытия в реальном времени в UI
        document.getElementById('aqn-opt-blur').addEventListener('input', (e) => {
            document.getElementById('aqn-blur-val').textContent = e.target.value;
        });

        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();
        document.getElementById('aqn-opt-color').value = settings.bgColor;
        document.getElementById('aqn-opt-opacity').value = settings.bgOpacity;
        const blurInput = document.getElementById('aqn-opt-blur');
        blurInput.value = settings.blur;
        document.getElementById('aqn-blur-val').textContent = settings.blur;

        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)
        };
        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: center;
                pointer-events: none; /* Пропуск кликов сквозь контейнер */
                transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
            }
            #custom-quick-nav > * { pointer-events: auto; }

            @media (max-width: 800px) {
                #custom-quick-nav { justify-content: flex-start; }
            }

            #custom-quick-nav::-webkit-scrollbar { display: none; }
            #aqn-spacer { width: 100%; display: block; flex-shrink: 0; }

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

            /* Стили модального окна */
            #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: 24px; border-radius: 12px;
                width: 450px; max-width: 90%; max-height: 85vh;
                display: flex; flex-direction: column; gap: 16px;
                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-visual-settings {
                background: #121212; padding: 12px; border-radius: 8px; border: 1px solid #333;
                display: flex; flex-direction: column; gap: 12px; font-size: 13px;
            }
            .aqn-setting-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }
            .aqn-setting-row label { display: flex; align-items: center; gap: 8px; cursor: pointer; color: #aaa; }
            .aqn-setting-row input[type="color"] { background: none; border: none; width: 30px; height: 30px; cursor: pointer; padding: 0; border-radius: 4px; }
            .aqn-setting-row input[type="range"] { cursor: pointer; width: 100px; }
            #aqn-blur-val { display: inline-block; width: 20px; text-align: right; color: #fff; }

            /* Списки ссылок */
            #aqn-links-list { display: flex; flex-direction: column; gap: 10px; overflow-y: auto; max-height: 40vh; padding-right: 4px; }
            #aqn-links-list::-webkit-scrollbar { width: 6px; }
            #aqn-links-list::-webkit-scrollbar-track { background: #121212; border-radius: 4px; }
            #aqn-links-list::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
            .aqn-link-row { display: flex; gap: 8px; align-items: center; }
            .aqn-link-row input { background: #121212; border: 1px solid #333; color: #fff; padding: 8px 12px; border-radius: 6px; outline: none; transition: border-color 0.2s; }
            .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; }
            .aqn-del-btn { background: #3a1a1a; color: #ff5555; padding: 8px 12px; }
            .aqn-del-btn:hover { background: #ff5555; color: #fff; }
            #aqn-add-link-btn { background: #2a2a2a; color: #aaa; padding: 10px; width: 100%; border: 1px dashed #444; }
            #aqn-add-link-btn:hover { background: #333; color: #fff; }
            .aqn-modal-actions { display: flex; justify-content: space-between; gap: 8px; margin-top: 8px; padding-top: 16px; border-top: 1px solid #333; }
            #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();
    }
})();