// ==UserScript==
// @name (v3.4 Simple+Border+TopNav+Glow+Gradient+Font) Стиль для BR Forum
// @namespace http://tampermonkey.net/
// @version 3.4
// @description Простая настройка фона, цвета, окантовки, верхней навигации, свечения текста, анимированного градиента и шрифта для forum.blackrussia.online.
// @author M. Ageev
// @match https://forum.blackrussia.online/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log('[BR Style+Border+TopNav+Glow+Gradient+Font v3.4] Инициализация...');
const STYLE_ID = 'blackrussia-custom-style-v34';
const PANEL_ID = 'blackrussia-settings-panel-v34';
const TOP_NAV_ID = 'blackrussia-top-nav-bar-v34';
const TOP_NAV_HEIGHT = '45px';
let settingsPanel = null;
let currentSettings = {};
// --- Стандартные Настройки (с окантовкой, градиентом и шрифтом) ---
const defaultSettings = {
bgImageUrl: '',
opacityValue: 0.9,
borderRadius: '8px',
bgColor: '#2E2E2E',
enableRounding: true,
enableEdge: true,
edgeColor: '#FFEB3B',
edgeWidth: '1px',
edgeOpacity: 0.7,
// Новые настройки градиента
enableGradient: false,
gradientColors: ['#ff0000', '#00ff00', '#0000ff'],
gradientSpeed: 20,
// Новая настройка шрифта
fontFamily: '' // По умолчанию шрифт не изменен
};
// --- Вспомогательные Функции ---
function hexToRgb(hex) {
if (!hex || typeof hex !== 'string') return null;
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// --- Управление Настройками ---
async function loadSettings() {
console.log('[BR Style] Загрузка настроек...');
currentSettings = {};
try {
for (const key in defaultSettings) {
const savedValue = await GM_getValue(key, defaultSettings[key]);
if (typeof defaultSettings[key] === 'boolean') {
currentSettings[key] = (savedValue === true || savedValue === 'true');
} else if (key === 'opacityValue' || key === 'edgeOpacity') {
currentSettings[key] = parseFloat(savedValue) || defaultSettings[key];
} else if (typeof defaultSettings[key] === 'number') {
currentSettings[key] = parseInt(savedValue, 10) || defaultSettings[key];
} else if (key === 'gradientColors') {
currentSettings[key] = typeof savedValue === 'string' ?
savedValue.split(',') :
defaultSettings[key];
} else {
currentSettings[key] = savedValue;
}
}
console.log('[BR Style] Настройки загружены:', currentSettings);
} catch (e) {
console.error('[BR Style] Ошибка загрузки настроек!', e);
currentSettings = { ...defaultSettings };
alert('[BR Style] Ошибка загрузки настроек! Применены стандартные значения.');
}
}
async function saveSettings(settingsToSave) {
console.log('[BR Style] Сохранение настроек...');
try {
for (const key in settingsToSave) {
if (defaultSettings.hasOwnProperty(key)) {
await GM_setValue(key, settingsToSave[key]);
}
}
currentSettings = { ...settingsToSave };
console.log('[BR Style] Настройки сохранены.');
return true;
} catch (e) {
console.error('[BR Style] Ошибка сохранения настроек!', e);
alert('[BR Style] Ошибка сохранения настроек!');
return false;
}
}
// --- Применение Динамических Стилей Форума (на основе настроек) ---
function applyForumStyles(settings) {
console.log('[BR Style] Применение динамических стилей...');
let styleElement = document.getElementById(STYLE_ID);
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = STYLE_ID;
styleElement.type = 'text/css';
(document.head || document.documentElement).appendChild(styleElement);
}
try {
const bgRgb = hexToRgb(settings.bgColor);
const elementBgColor = bgRgb ?
`rgba(${bgRgb.r}, ${bgRgb.g}, ${bgRgb.b}, ${settings.opacityValue})` :
defaultSettings.bgColor;
const edgeRgb = hexToRgb(settings.edgeColor);
const edgeColorWithOpacity = edgeRgb ?
`rgba(${edgeRgb.r}, ${edgeRgb.g}, ${edgeRgb.b}, ${settings.edgeOpacity})` :
'transparent';
const finalEdgeBoxShadow = settings.enableEdge ?
`0 0 0 ${settings.edgeWidth} ${edgeColorWithOpacity}` : 'none';
const finalBorderRadius = settings.enableRounding ? settings.borderRadius : '0px';
const fallbackBgColor = settings.bgColor || '#1e1e1e';
// Анимированный градиент
const gradientCss = settings.enableGradient ? `
body {
background: linear-gradient(-45deg, ${settings.gradientColors.join(',')});
background-size: 400% 400%;
animation: gradient ${settings.gradientSpeed}s ease infinite;
}
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
` : '';
const mainElementsSelector = `
.block-container, .block-filterBar, .message-inner,
.widget-container .widget, .bbCodeBlock-content, .formPopup .menu-content,
.tooltip-content, .structItem, .notice-content, .overlay-container .overlay-content,
.p-header, .p-nav, .p-navSticky.is-sticky .p-nav, .p-footer
`;
const forumCss = `
${gradientCss}
body:not([style*="background-image"]) {
background-color: ${fallbackBgColor} !important;
}
${!settings.enableGradient && settings.bgImageUrl ? `
body {
background-image: url('${settings.bgImageUrl}') !important;
background-size: cover !important;
background-attachment: fixed !important;
}
` : ''}
.p-pageWrapper {
background-color: ${elementBgColor} !important;
border-radius: ${finalBorderRadius} !important;
box-shadow: ${finalEdgeBoxShadow} !important;
${settings.enableRounding ? 'overflow: hidden;' : ''}
}
${mainElementsSelector} {
background-color: ${elementBgColor} !important;
border-radius: ${finalBorderRadius} !important;
box-shadow: ${finalEdgeBoxShadow} !important;
${settings.enableRounding ? 'overflow: hidden;' : ''}
}
.p-body-inner, .message, .message-cell, .block-body, .bbCodeBlock,
.widget-container, .notice, .overlay-container .overlay {
background: none !important;
border: none !important;
box-shadow: none !important;
}
/* Применяем выбранный шрифт ко всему body */
body {
font-family: ${settings.fontFamily || 'inherit'} !important;
}
`;
styleElement.textContent = forumCss;
} catch (e) {
console.error('[BR Style] Ошибка применения стилей!', e);
}
}
// --- UI Панель Настроек ---
function createPanel() {
console.log('[BR Style] Создание панели настроек...');
if (document.getElementById(PANEL_ID)) return document.getElementById(PANEL_ID);
try {
settingsPanel = document.createElement('div');
settingsPanel.id = PANEL_ID;
settingsPanel.innerHTML = `
<h3>🎨 Настройки Стиля (v3.4)</h3>
<div class="setting-group">
<label for="s_fontFamily">Шрифт:</label>
<input type="text" id="s_fontFamily" name="fontFamily" placeholder="Например: Arial, sans-serif">
<small>Введите название шрифта (как в CSS).</small>
</div>
<div class="setting-group">
<input type="checkbox" id="s_enableGradient" name="enableGradient">
<label for="s_enableGradient" class="inline-label">Анимированный градиент</label>
<div class="sub-settings">
<div>
<label for="s_gradientColors">Цвета градиента (через запятую):</label>
<input type="text" id="s_gradientColors" name="gradientColors"
placeholder="Например: #ff0000, #00ff00, #0000ff">
</div>
<div style="margin-top: 8px;">
<label for="s_gradientSpeed">Скорость анимации (секунды):</label>
<input type="number" id="s_gradientSpeed" name="gradientSpeed"
min="5" max="60" step="1">
</div>
</div>
</div>
<div class="setting-group">
<label for="s_bgImageUrl_simple">URL Фона:</label>
<input type="text" id="s_bgImageUrl_simple" name="bgImageUrl" placeholder="Ссылка на картинку...">
</div>
<div class="setting-group">
<label for="s_bgColor_simple">Цвет Фона Элементов:</label>
<input type="color" id="s_bgColor_simple" name="bgColor">
</div>
<div class="setting-group">
<label for="s_opacityValue_simple">Прозрачность Фона Элементов (0-1):</label>
<input type="number" id="s_opacityValue_simple" name="opacityValue" min="0" max="1" step="0.05">
</div>
<hr>
<div class="setting-group">
<input type="checkbox" id="s_enableRounding_simple" name="enableRounding">
<label for="s_enableRounding_simple" class="inline-label">Включить скругление</label>
<div class="sub-settings">
<label for="s_borderRadius_simple">Радиус Скругления:</label>
<input type="text" id="s_borderRadius_simple" name="borderRadius" placeholder="Например: 8px, 10px">
</div>
</div>
<hr>
<div class="setting-group">
<input type="checkbox" id="s_enableEdge_simple" name="enableEdge">
<label for="s_enableEdge_simple" class="inline-label">Цветная Окантовка</label>
<div class="sub-settings">
<div>
<label for="s_edgeColor_simple">Цвет Окантовки:</label>
<input type="color" id="s_edgeColor_simple" name="edgeColor">
</div>
<div style="margin-top: 8px;">
<label for="s_edgeWidth_simple">Толщина Окантовки:</label>
<input type="text" id="s_edgeWidth_simple" name="edgeWidth" placeholder="Например: 1px, 2px">
</div>
<div style="margin-top: 8px;">
<label for="s_edgeOpacity_simple">Прозрачность Окантовки (0-1):</label>
<input type="number" id="s_edgeOpacity_simple" name="edgeOpacity" min="0" max="1" step="0.05">
</div>
</div>
</div>
<div class="button-group">
<button id="save-btn-simple">Сохранить</button>
<button id="close-btn-simple">Закрыть</button>
</div>
`;
document.body.appendChild(settingsPanel);
console.log('[BR Style] Панель настроек создана.');
// Логика кнопок
settingsPanel.querySelector('#save-btn-simple').addEventListener('click', async () => {
console.log('[BR Style] Нажата кнопка Сохранить.');
const newSettings = {};
const inputs = settingsPanel.querySelectorAll('input[name]');
inputs.forEach(input => {
const key = input.name;
if (defaultSettings.hasOwnProperty(key)) {
if (input.type === 'checkbox') {
newSettings[key] = input.checked;
} else if (input.type === 'number') {
newSettings[key] = parseFloat(input.value) || defaultSettings[key];
if (key === 'opacityValue' || key === 'edgeOpacity') {
newSettings[key] = Math.max(0, Math.min(1, newSettings[key]));
} else if (key === 'gradientSpeed') {
newSettings[key] = parseInt(input.value, 10) || defaultSettings[key];
}
} else if (key === 'gradientColors') {
newSettings[key] = input.value.split(',').map(s => s.trim());
} else if (key === 'fontFamily') {
newSettings[key] = input.value.trim();
} else {
newSettings[key] = input.value;
}
}
});
const success = await saveSettings(newSettings);
if (success) {
applyForumStyles(currentSettings); // Применяем динамические стили
closePanel();
}
});
settingsPanel.querySelector('#close-btn-simple').addEventListener('click', () => {
console.log('[BR Style] Нажата кнопка Закрыть.');
closePanel();
});
return settingsPanel;
} catch (e) {
console.error('[BR Style] Ошибка создания панели настроек!', e);
alert('[BR Style] Не удалось создать панель настроек!');
return null;
}
}
function openPanel() {
console.log('[BR Style] Открытие панели настроек...');
try {
if (!settingsPanel) {
settingsPanel = createPanel();
if (!settingsPanel) return;
}
const inputs = settingsPanel.querySelectorAll('input[name]');
inputs.forEach(input => {
const key = input.name;
if (currentSettings.hasOwnProperty(key)) {
if (input.type === 'checkbox') {
input.checked = currentSettings[key];
} else if (key === 'gradientColors') {
input.value = Array.isArray(currentSettings[key]) ? currentSettings[key].join(', ') : '';
} else {
input.value = currentSettings[key] ?? '';
}
}
});
settingsPanel.style.display = 'block';
console.log('[BR Style] Панель настроек открыта.');
} catch (e) {
console.error('[BR Style] Ошибка открытия панели настроек!', e);
alert('[BR Style] Не удалось открыть панель настроек!');
}
}
function closePanel() {
if (settingsPanel) {
settingsPanel.style.display = 'none';
console.log('[BR Style] Панель настроек закрыта.');
}
}
// --- Добавление HTML для верхней навигационной панели ---
function addTopNavBarHTML() {
console.log('[BR TopNav] Добавление HTML верхней панели...');
if (document.getElementById(TOP_NAV_ID)) return;
try {
const topNav = document.createElement('nav');
topNav.id = TOP_NAV_ID;
topNav.className = 'br-top-nav-bar';
// --- ЗАМЕНИТЕ ЭТИ ССЫЛКИ И НАЗВАНИЯ ---
const link1_href = "https://forum.blackrussia.online/";
const link1_text = "Главная Форума";
const link2_href = "https://forum.blackrussia.online/index.php?forums/%D0%9F%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D0%B0-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80%D0%BE%D0%B2.10/";
const link2_text = "Правила";
const link3_href = "https://forum.blackrussia.online/index.php?forums/%D0%96%D0%B0%D0%BB%D0%BE%D0%B1%D1%8B.14/";
const link3_text = "Жалобы";
// ---------------------------------------
topNav.innerHTML = `
<a href="${link1_href}">${link1_text}</a>
<a href="${link2_href}">${link2_text}</a>
<a href="${link3_href}">${link3_text}</a>
`;
document.body.insertBefore(topNav, document.body.firstChild);
console.log('[BR TopNav] HTML верхней панели добавлен.');
} catch (e) {
console.error('[BR TopNav] Ошибка добавления HTML верхней панели!', e);
alert('[BR TopNav] Не удалось добавить верхнюю панель навигации!');
}
}
// --- Внедрение Статичных CSS (Панель настроек, Верхняя панель, СВЕЧЕНИЕ ТЕКСТА) ---
function injectStaticStyles() {
console.log('[BR Style] Внедрение статичных CSS...');
try {
const staticCss = `
/* ============ ДОБАВЛЕНО: Легкое свечение для ВСЕГО текста ============ */
* {
/* Белое свечение: смещение 0 0, размытие 5px, цвет белый с 60% непрозрачностью */
/* Вы можете настроить размытие (5px) и прозрачность (0.6) */
text-shadow: 0 0 5px rgba(255, 255, 255, 0.6);
/* Альтернатива: светло-серое свечение (если белый слишком яркий) */
/* text-shadow: 0 0 5px rgba(200, 200, 200, 0.5); */
}
/* =================================================================== */
/* === Стили для Верхней Навигационной Панели === */
#${TOP_NAV_ID} {
background-color: #222;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
height: ${TOP_NAV_HEIGHT};
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
padding: 0 15px;
box-sizing: border-box;
}
#${TOP_NAV_ID} a {
color: #eee;
text-decoration: none;
padding: 0 15px;
font-size: 16px;
font-weight: bold;
line-height: ${TOP_NAV_HEIGHT};
transition: color 0.2s ease;
/* Если свечение на ссылках панели мешает, раскомментируйте строку ниже */
/* text-shadow: none !important; */
}
#${TOP_NAV_ID} a:hover {
color: #FFEB3B;
}
/* === Отступ для контента из-за фиксированной верхней панели === */
.p-pageWrapper {
margin-top: ${TOP_NAV_HEIGHT} !important;
}
/* === Стили для Панели Настроек (desktop) === */
#${PANEL_ID} {
position: fixed; z-index: 9999; bottom: 10px; left: 10px; width: 300px;
background: #333; color: #eee; padding: 15px; border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,0.5); display: none; border: 1px solid #555;
font-family: sans-serif; font-size: 13px; max-height: calc(100vh - 30px); overflow-y: auto;
text-shadow: none !important;
}
#${PANEL_ID} * {
text-shadow: none !important;
}
#${PANEL_ID} h3 { margin: 0 0 15px; text-align: center; font-size: 16px; border-bottom: 1px solid #555; padding-bottom: 8px;}
#${PANEL_ID} div.setting-group { margin-bottom: 12px; }
#${PANEL_ID} label { display: block; margin-bottom: 4px; font-weight: bold; color: #ccc; }
#${PANEL_ID} input[type="text"], #${PANEL_ID} input[type="number"] { width: calc(100% - 12px); padding: 5px; background: #444; border: 1px solid #666; color: #eee; border-radius: 3px; box-sizing: border-box; }
#${PANEL_ID} input[type="color"] { padding: 0; border: 1px solid #666; height: 25px; width: 35px; vertical-align: middle; margin-left: 5px; border-radius: 3px; cursor: pointer;}
#${PANEL_ID} input[type="checkbox"] { vertical-align: middle; margin-right: 5px; }
#${PANEL_ID} label.inline-label { display: inline; font-weight: normal; vertical-align: middle; }
#${PANEL_ID} .button-group { margin-top: 15px; text-align: right; border-top: 1px solid #555; padding-top: 10px; }
#${PANEL_ID} button { padding: 6px 12px; margin-left: 8px; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;}
#${PANEL_ID} #save-btn-simple { background-color: #4CAF50; color: white; }
#${PANEL_ID} #close-btn-simple { background-color: #f44336; color: white; }
#${PANEL_ID} hr { border: none; border-top: 1px solid #555; margin: 15px 0; }
#${PANEL_ID} .sub-settings { margin-left: 20px; padding-left: 10px; border-left: 2px solid #555; margin-top: 8px; }
/* === Стили для Панели Настроек (mobile) === */
@media (max-width: 768px) {
#${PANEL_ID} {
position: fixed;
z-index: 9999;
bottom: 0; /* Располагаем внизу */
left: 0;
width: 100%; /* Занимает всю ширину экрана */
margin: 0; /* Убираем отступы */
border-radius: 0; /* Убираем скругление */
box-shadow: 0 -3px 10px rgba(0,0,0,0.5); /* Тень сверху */
padding: 10px;
font-size: 14px; /* Увеличиваем шрифт */
max-height: 80vh; /* Ограничиваем высоту, чтобы не занимать весь экран */
overflow-y: auto;
}
#${PANEL_ID} h3 {
font-size: 18px;
margin-bottom: 10px;
}
#${PANEL_ID} div.setting-group {
margin-bottom: 15px;
}
#${PANEL_ID} label {
font-size: 15px;
margin-bottom: 6px;
}
#${PANEL_ID} input[type="text"],
#${PANEL_ID} input[type="number"],
#${PANEL_ID} input[type="color"] {
padding: 8px; /* Увеличиваем отступы */
font-size: 16px; /* Увеличиваем шрифт */
}
#${PANEL_ID} input[type="color"] {
height: 30px;
width: 40px;
}
#${PANEL_ID} button {
padding: 10px 16px; /* Увеличиваем отступы */
font-size: 16px; /* Увеличиваем шрифт */
margin-left: 10px;
}
#${PANEL_ID} .sub-settings {
margin-left: 15px;
padding-left: 10px;
border-left: 2px solid #555;
margin-top: 10px;
}
#${PANEL_ID} hr {
margin: 10px 0;
}
#${PANEL_ID} .button-group {
text-align: center; /* Кнопки по центру */
padding-top: 15px;
}
#${PANEL_ID} .button-group button {
margin: 5px; /* Увеличиваем отступы между кнопками */
}
}
`;
GM_addStyle(staticCss);
console.log('[BR Style] Статичные CSS внедрены (включая свечение текста и стили для мобильных).');
} catch (e) {
console.error('[BR Style] Ошибка внедрения статичных CSS!', e);
alert('[BR Style] Ошибка внедрения статичных CSS!');
}
}
// --- Инициализация Скрипта ---
async function initialize() {
try {
injectStaticStyles(); // Внедряем статичные CSS (панель, верхняя панель, ОТСТУП, СВЕЧЕНИЕ ТЕКСТА, МОБИЛЬНЫЕ СТИЛИ)
addTopNavBarHTML();
await loadSettings();
applyForumStyles(currentSettings); // Применяем динамические стили (фон, окантовка, скругление, градиент, шрифт)
GM_registerMenuCommand('🎨 Настроить стиль (+Окантовка)', openPanel, 'b');
console.log('[BR Style+Border+TopNav+Glow+Gradient+Font v3.4] Инициализация завершена.');
} catch (e) {
console.error('[BR Style] Ошибка инициализации!', e);
alert('[BR Style] Ошибка инициализации скрипта!');
}
}
// --- Запуск ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();