Animesss Quick Nav

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

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Advertisement:

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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