ASSSQuick Nav

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Advertisement:

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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