Добавление настраиваемых кнопок быстрого доступа под шапку с настройками фона
// ==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();
}
})();