ASSSQuick Nav

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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