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