// ==UserScript==
// @name Падающие листья, есть на выбор, облака, дождь
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Adds persistent falling particles (leaves, petals, rain) with advanced controls (wind, glow, fog, selection, RGB) and realistic clouds to forum.blackrussia.online
// @author Тех. [06] M. Ageev
// @match https://forum.blackrussia.online/*
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- КОНФИГУРАЦИЯ И НАСТРОЙКИ ПО УМОЛЧАНИЮ ---
const allParticleTypes = ['🌸', '🍁', '🍂', '🍃', '🌿', '✨', '💧'];
const defaultSettings = {
effectEnabled: true,
panelVisible: false,
// Основные частицы
density: 50,
speed: 1.5,
size: 20,
wind: 0.3,
selectedTypes: ['🌸', '🍁', '🍂', '🍃'],
// Свечение
glowEnabled: false,
glowIntensity: 5,
rgbEnabled: false,
glowColors: ['red', 'green', 'blue'],
rgbSpeed: 1,
// Туман
fogDensity: 0,
// Дождь
rainEnabled: false,
rainDensity: 20,
rainSpeed: 3,
rainDropLength: 15,
// Облака
cloudsEnabled: false,
cloudDensity: 10,
cloudSpeed: 0.2,
cloudColor: 'rgba(255, 255, 255, 0.3)',
// Внутренние переменные
currentColorIndex: 0,
colorCycleCounter: 0,
};
// --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ---
let settings = {};
let particles = [];
let clouds = [];
let canvas, ctx;
let animationFrameId;
let controlPanel, settingsButton;
// --- УПРАВЛЕНИЕ НАСТРОЙКАМИ ---
async function loadSettings() {
const savedSettings = await GM_getValue('fallingLeavesSettings_v2_3', defaultSettings); // Используем тот же ключ для совместимости
settings = { ...defaultSettings, ...savedSettings };
if (!Array.isArray(settings.selectedTypes)) {
settings.selectedTypes = defaultSettings.selectedTypes;
}
if (!Array.isArray(settings.glowColors)) {
settings.glowColors = defaultSettings.glowColors;
}
console.log("Falling Leaves Settings Loaded:", settings);
}
async function saveSettings() {
await GM_setValue('fallingLeavesSettings_v2_3', settings);
}
// --- СОЗДАНИЕ И НАСТРОЙКА CANVAS ---
function setupCanvas() {
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.zIndex = '9998';
canvas.style.pointerEvents = 'none';
canvas.style.display = settings.effectEnabled ? 'block' : 'none';
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
}
function resizeCanvas() {
if (canvas) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
}
// --- УПРАВЛЕНИЕ ЧАСТИЦАМИ ---
function createParticle(type) {
let p = {
x: Math.random() * canvas.width,
y: -Math.random() * canvas.height * 0.5,
opacity: 0.7 + Math.random() * 0.3,
rotation: Math.random() * 360,
rotationSpeed: (Math.random() - 0.5) * 2
};
if (settings.selectedTypes.length > 0 && type === 'default') {
const availableTypes = settings.selectedTypes;
p.char = availableTypes[Math.floor(Math.random() * availableTypes.length)];
const sizeVariation = (Math.random() - 0.5) * settings.size * 0.5;
const speedVariation = (Math.random() - 0.5) * settings.speed * 0.5;
const baseSpeed = Math.max(0.5, Math.min(5, settings.speed + speedVariation));
p.size = Math.max(10, Math.min(40, settings.size + sizeVariation));
p.speedY = baseSpeed;
p.speedX = (settings.wind / 2 + (Math.random() - 0.5) * settings.wind) * baseSpeed * 0.5;
} else if (type === 'rain') {
p.speedY = settings.rainSpeed;
p.speedX = (Math.random() - 0.5) * 0.5;
p.length = settings.rainDropLength;
p.weight = Math.random() * 0.3 + 0.9;
p.angle = Math.PI / 6 + (Math.random() - 0.5) * Math.PI / 12; // Slightly angled
}
return p;
}
function createParticles() {
particles = [];
const defaultParticleCount = Math.min(Math.max(0, settings.density), 150);
const rainParticleCount = settings.rainEnabled ? Math.min(Math.max(0, settings.rainDensity), 150) : 0;
for (let i = 0; i < defaultParticleCount; i++) {
particles.push(createParticle('default'));
}
for (let i = 0; i < rainParticleCount; i++) {
particles.push(createParticle('rain'));
}
}
function updateParticles() {
if (!ctx || !settings.effectEnabled) return;
particles.forEach((p, index) => {
if (p.length) { // Дождь
p.y += p.speedY * p.weight;
p.x += p.speedX;
if (p.y > canvas.height) {
particles[index] = createParticle('rain');
particles[index].y = -20;
}
} else { // Обычные частицы
p.y += p.speedY;
p.x += p.speedX;
p.rotation += p.rotationSpeed;
const bufferZoneX = Math.abs(settings.wind * 50);
if (p.y > canvas.height + p.size || p.x < -p.size - bufferZoneX || p.x > canvas.width + p.size + bufferZoneX) {
particles[index] = createParticle('default');
particles[index].y = -p.size;
particles[index].x = Math.random() * canvas.width;
}
}
});
}
// --- УПРАВЛЕНИЕ ОБЛАКАМИ ---
function createCloudParticle() {
const numCircles = 3 + Math.floor(Math.random() * 3);
const circleProps = [];
for (let i = 0; i < numCircles; i++) {
circleProps.push({
sizeVariation: (Math.random() - 0.5) * 150 * 0.3,
yOffset: (Math.random() - 0.5) * 150 * 0.2
});
}
return {
x: -Math.random() * canvas.width * 0.5,
y: Math.random() * canvas.height * 0.4, // Clouds in the upper part
size: 50 + Math.random() * 150,
opacity: 0.2 + Math.random() * 0.2,
drift: settings.cloudSpeed + Math.random() * 0.1,
numCircles: numCircles,
circleProps: circleProps
};
}
function createClouds() {
clouds = [];
const cloudCount = Math.min(Math.max(0, settings.cloudDensity), 50);
for (let i = 0; i < cloudCount; i++) {
clouds.push(createCloudParticle());
}
}
function updateClouds() {
if (!settings.cloudsEnabled) return;
clouds.forEach(cloud => {
cloud.x += cloud.drift;
if (cloud.x > canvas.width + cloud.size * 0.5) {
cloud.x = -cloud.size * 0.5;
}
});
}
function drawParticles() {
if (!ctx || !settings.effectEnabled) return;
ctx.clearRect(0, 0, canvas.width, canvas.height); // Очищаем предыдущий кадр
// --- РИСУЕМ ОБЛАКА (если включены) ---
if (settings.cloudsEnabled) {
clouds.forEach(cloud => {
ctx.globalAlpha = cloud.opacity;
ctx.fillStyle = settings.cloudColor;
for (let i = 0; i < cloud.numCircles; i++) {
const props = cloud.circleProps[i];
ctx.beginPath();
ctx.ellipse(cloud.x + cloud.size * 0.5 * (i / (cloud.numCircles - 1)) - cloud.size * 0.25, cloud.y + props.yOffset, Math.max(10, cloud.size * 0.3 + props.sizeVariation), Math.max(5, cloud.size * 0.2 + props.sizeVariation * 0.5), 0, 0, 2 * Math.PI);
ctx.fill();
}
});
ctx.globalAlpha = 1.0;
}
// --- РИСУЕМ ТУМАН (если включен) ---
if (settings.fogDensity > 0) {
const fogStartHeight = canvas.height * (1 - settings.fogDensity * 1.2);
const gradient = ctx.createLinearGradient(0, fogStartHeight, 0, canvas.height);
const fogColor = '220, 220, 230';
gradient.addColorStop(0, `rgba(${fogColor}, 0)`);
gradient.addColorStop(1, `rgba(${fogColor}, ${settings.fogDensity * 0.85})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// --- РИСУЕМ ЧАСТИЦЫ ---
if (settings.glowEnabled || settings.rgbEnabled) {
ctx.shadowBlur = settings.rgbEnabled ? settings.glowIntensity : (settings.glowEnabled ? settings.glowIntensity : 0);
ctx.shadowColor = settings.rgbEnabled ? settings.glowColors[settings.currentColorIndex] : (settings.glowEnabled ? 'rgba(255, 255, 220, 0.6)' : 'transparent');
} else {
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
}
particles.forEach(p => {
let particleAlpha = p.opacity;
if (settings.fogDensity > 0.1) {
const fadeStart = canvas.height * 0.6;
if (p.y > fadeStart) {
const fadeFactor = Math.max(0, 1 - (p.y - fadeStart) / (canvas.height - fadeStart));
particleAlpha *= Math.max(0.1, fadeFactor + (1-fadeFactor)*(1 - Math.sqrt(settings.fogDensity)));
}
}
ctx.save();
ctx.globalAlpha = particleAlpha;
if (p.length) { // Дождь
ctx.lineWidth = 2;
ctx.strokeStyle = 'rgba(0, 0, 200, 0.7)';
ctx.beginPath();
ctx.moveTo(p.x, p.y);
const endX = p.x + Math.sin(p.angle) * p.length;
const endY = p.y + Math.cos(p.angle) * p.length;
ctx.lineTo(endX, endY);
ctx.stroke();
} else if (p.char) { // Обычные частицы
ctx.font = `${p.size}px Arial`;
ctx.translate(p.x + p.size / 2, p.y + p.size / 2);
ctx.rotate(p.rotation * Math.PI / 180);
ctx.fillText(p.char, -p.size / 2, p.size / 2);
}
ctx.restore();
});
// Сброс тени и прозрачности после отрисовки
ctx.shadowBlur = 0;
ctx.globalAlpha = 1.0;
}
// --- АНИМАЦИОННЫЙ ЦИКЛ ---
function animate() {
updateParticles();
updateClouds();
drawParticles();
animationFrameId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
}
function startAnimation() {
if (!animationFrameId && settings.effectEnabled) {
createParticles();
createClouds();
animate();
}
}
// --- УПРАВЛЕНИЕ ЭФФЕКТОМ (ВКЛ/ВЫКЛ) ---
function setEffectEnabled(enabled) {
settings.effectEnabled = enabled;
if (canvas) {
canvas.style.display = enabled ? 'block' : 'none';
}
if (enabled) {
startAnimation();
} else {
stopAnimation();
}
updateControlsState();
saveSettings();
}
// --- ПАНЕЛЬ УПРАВЛЕНИЯ И ИКОНКА НАСТРОЕК ---
function createSettingsButton() {
settingsButton = document.createElement('button');
settingsButton.id = 'fallingLeavesSettingsButton';
settingsButton.innerHTML = '⚙️';
settingsButton.title = 'Настройки эффектов';
settingsButton.style.position = 'fixed';
settingsButton.style.bottom = '10px';
settingsButton.style.left = '10px';
settingsButton.style.zIndex = '10001';
settingsButton.style.background = 'rgba(30, 30, 30, 0.8)';
settingsButton.style.color = 'white';
settingsButton.style.border = 'none';
settingsButton.style.borderRadius = '5px';
settingsButton.style.width = '40px';
settingsButton.style.height = '40px';
settingsButton.style.fontSize = '20px';
settingsButton.style.lineHeight = '40px';
settingsButton.style.textAlign = 'center';
settingsButton.style.cursor = 'pointer';
settingsButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.5)';
settingsButton.style.transition = 'transform 0.2s ease';
settingsButton.addEventListener('mouseover', () => { settingsButton.style.transform = 'scale(1.1)'; });
settingsButton.addEventListener('mouseout', () => { settingsButton.style.transform = 'scale(1)'; });
settingsButton.addEventListener('click', toggleControlPanel);
document.body.appendChild(settingsButton);
}
function createControlPanel() {
controlPanel = document.createElement('div');
controlPanel.id = 'fallingLeavesControlPanel';
controlPanel.style.position = 'fixed';
controlPanel.style.bottom = '60px';
controlPanel.style.left = '10px';
controlPanel.style.backgroundColor = 'rgba(40, 40, 40, 0.9)';
controlPanel.style.color = 'white';
controlPanel.style.padding = '15px';
controlPanel.style.borderRadius = '8px';
controlPanel.style.zIndex = '10000';
controlPanel.style.fontFamily = 'Segoe UI, Tahoma, sans-serif';
controlPanel.style.fontSize = '14px';
controlPanel.style.minWidth = '300px'; /* Немного уменьшена ширина */
controlPanel.style.boxShadow = '0 4px 10px rgba(0,0,0,0.6)';
controlPanel.style.display = 'block';
controlPanel.style.transition = 'opacity 0.2s ease-out, visibility 0.2s ease-out';
controlPanel.style.opacity = settings.panelVisible ? '1' : '0';
controlPanel.style.visibility = settings.panelVisible ? 'visible' : 'hidden';
// --- Стили ---
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
styleSheet.innerText = `
/* ... (предыдущие стили @media и общие) ... */
@media (max-width: 600px) {
#fallingLeavesControlPanel {
font-size: 11px; /* Уменьшен шрифт */
padding: 10px;
min-width: 250px; /* Уменьшена минимальная ширина */
bottom: 55px;
}
#fallingLeavesControlPanel .control-row label,
#fallingLeavesControlPanel .particle-type-selector label {
display: block;
margin-bottom: 3px;
}
#fallingLeavesControlPanel input[type="range"] { width: 100%; }
#fallingLeavesControlPanel .particle-type-selector { grid-template-columns: repeat(3, 1fr); gap: 5px;}
}
#fallingLeavesControlPanel input[type="range"] {
width: 160px; height: 8px; vertical-align: middle; margin: 0 8px; cursor: pointer;
background: #555; border-radius: 5px; -webkit-appearance: none;
}
#fallingLeavesControlPanel input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; border: none; height: 14px; width: 14px; border-radius: 50%; background: #eee; cursor: pointer;
}
#fallingLeavesControlPanel input[type="range"]::-moz-range-thumb {
border: none; height: 14px; width: 14px; border-radius: 50%; background: #eee; cursor: pointer;
}
#fallingLeavesControlPanel .control-row, #fallingLeavesControlPanel .checkbox-row {
margin-bottom: 10px; display: flex; align-items: center; flex-wrap: wrap;
}
#fallingLeavesControlPanel .control-row label { min-width: 90px; display: inline-block; margin-right: 10px; color: #ddd; }
#fallingLeavesControlPanel .value-display { min-width: 40px; display: inline-block; text-align: right; font-weight: bold; color: #eee; }
#fallingLeavesControlPanel hr { border: none; border-top: 1px solid rgba(255,255,255,0.1); margin: 12px 0; }
#fallingLeavesControlPanel input[type="checkbox"] { margin-right: 8px; cursor: pointer; vertical-align: middle;}
#fallingLeavesControlPanel .particle-type-selector {
margin-top: 8px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(40px, 1fr)); /* Более компактное размещение */
gap: 6px;
font-size: 18px;
}
#fallingLeavesControlPanel .particle-type-selector label {
display: flex;
align-items: center;
cursor: pointer;
color: #ccc;
}
#fallingLeavesControlPanel .color-input-row {
display: flex;
align-items: center;
margin-bottom: 8px;
}
#fallingLeavesControlPanel .color-input-row label {
min-width: 120px;
margin-right: 10px;
color: #ddd;
}
#fallingLeavesControlPanel .color-input {
flex-grow: 1;
padding: 6px;
border: 1px solid #777;
border-radius: 4px;
background-color: #333;
color: #eee;
font-size: 13px;
}
#fallingLeavesControlPanel fieldset {
border: 1px solid rgba(255,255,255,0.2);
border-radius: 4px;
padding: 8px 12px 12px 12px;
margin-top: 10px;
}
#fallingLeavesControlPanel legend {
padding: 0 8px;
color: #bbb;
}
#fallingLeavesControlPanel .effect-group {
margin-bottom: 15px;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 15px;
}
#fallingLeavesControlPanel .effect-group:last-child {
border-bottom: none;
}
#fallingLeavesControlPanel .effect-title {
font-weight: bold;
color: #eee;
margin-bottom: 8px;
}
`;
document.head.appendChild(styleSheet);
// --- Элементы управления ---
// 1. Вкл/Выкл Эффекта
const enableRow = document.createElement('div');
enableRow.className = 'checkbox-row';
const enableLabel = document.createElement('label');
enableLabel.htmlFor = 'effectEnabledCheckbox';
enableLabel.textContent = 'Включить все эффекты:';
enableLabel.style.fontWeight = 'bold';
const enableCheckbox = document.createElement('input');
enableCheckbox.type = 'checkbox';
enableCheckbox.id = 'effectEnabledCheckbox';
enableCheckbox.checked = settings.effectEnabled;
enableCheckbox.addEventListener('change', (e) => setEffectEnabled(e.target.checked));
enableRow.appendChild(enableCheckbox);
enableRow.appendChild(enableLabel);
controlPanel.appendChild(enableRow);
controlPanel.appendChild(document.createElement('hr'));
// --- Группа "Основные частицы" ---
const defaultParticlesGroup = document.createElement('div');
defaultParticlesGroup.className = 'effect-group';
const defaultParticlesTitle = document.createElement('div');
defaultParticlesTitle.className = 'effect-title';
defaultParticlesTitle.textContent = 'Основные частицы';
defaultParticlesGroup.appendChild(defaultParticlesTitle);
defaultParticlesGroup.appendChild(createSliderRow('Плотность:', 'densitySlider', 0, 150, settings.density, 1, (value) => {
settings.density = value;
createParticles();
if (settings.density === 0 && !settings.rainEnabled && !settings.cloudsEnabled && animationFrameId) stopAnimation();
else if ((settings.density > 0 || settings.rainEnabled || settings.cloudsEnabled) && !animationFrameId && settings.effectEnabled) startAnimation();
saveSettings();
}));
defaultParticlesGroup.appendChild(createSliderRow('Скорость:', 'speedSlider', 0.1, 5, settings.speed, 0.1, (value) => {
settings.speed = value; saveSettings();
}));
defaultParticlesGroup.appendChild(createSliderRow('Размер:', 'sizeSlider', 10, 40, settings.size, 1, (value) => {
settings.size = value; saveSettings();
}));
defaultParticlesGroup.appendChild(createSliderRow('Ветер:', 'windSlider', -2, 2, settings.wind, 0.1, (value) => {
settings.wind = value; saveSettings();
}));
const typesFieldset = document.createElement('fieldset');
const typesLegend = document.createElement('legend');
typesLegend.textContent = 'Выбор типов';
typesFieldset.appendChild(typesLegend);
const typesContainer = document.createElement('div');
typesContainer.className = 'particle-type-selector';
allParticleTypes.forEach(type => {
const typeLabel = document.createElement('label');
const typeCheckbox = document.createElement('input');
typeCheckbox.type = 'checkbox';
typeCheckbox.value = type;
typeCheckbox.checked = settings.selectedTypes.includes(type);
typeCheckbox.addEventListener('change', (e) => {
const char = e.target.value;
if (e.target.checked) {
if (!settings.selectedTypes.includes(char)) { settings.selectedTypes.push(char); }
} else {
settings.selectedTypes = settings.selectedTypes.filter(t => t !== char);
}
saveSettings();
});
typeLabel.appendChild(typeCheckbox);
typeLabel.appendChild(document.createTextNode(type));
typesContainer.appendChild(typeLabel);
});
typesFieldset.appendChild(typesContainer);
defaultParticlesGroup.appendChild(typesFieldset);
controlPanel.appendChild(defaultParticlesGroup);
// --- Группа "Атмосферные эффекты" ---
const atmosphericGroup = document.createElement('div');
atmosphericGroup.className = 'effect-group';
const atmosphericTitle = document.createElement('div');
atmosphericTitle.className = 'effect-title';
atmosphericTitle.textContent = 'Атмосферные эффекты';
atmosphericGroup.appendChild(atmosphericTitle);
atmosphericGroup.appendChild(createSliderRow('Туман:', 'fogSlider', 0, 100, settings.fogDensity * 100, 1, (value) => {
settings.fogDensity = value / 100;
saveSettings();
}));
const rainRow = document.createElement('div');
rainRow.className = 'checkbox-row';
const rainLabel = document.createElement('label');
rainLabel.htmlFor = 'rainEnabledCheckbox';
rainLabel.textContent = 'Дождь:';
const rainCheckbox = document.createElement('input');
rainCheckbox.type = 'checkbox';
rainCheckbox.id = 'rainEnabledCheckbox';
rainCheckbox.checked = settings.rainEnabled;
rainCheckbox.addEventListener('change', (e) => {
settings.rainEnabled = e.target.checked;
createParticles();
if ((settings.density > 0 || settings.rainEnabled || settings.cloudsEnabled) && !animationFrameId && settings.effectEnabled) startAnimation();
else if (!settings.density && !settings.rainEnabled && !settings.cloudsEnabled && animationFrameId) stopAnimation();
saveSettings();
});
rainRow.appendChild(rainCheckbox);
rainRow.appendChild(rainLabel);
atmosphericGroup.appendChild(rainRow);
atmosphericGroup.appendChild(createSliderRow(' Плотность дождя:', 'rainDensitySlider', 0, 150, settings.rainDensity, 1, (value) => {
settings.rainDensity = value; createParticles(); saveSettings();
}));
atmosphericGroup.appendChild(createSliderRow(' Скорость дождя:', 'rainSpeedSlider', 1, 10, settings.rainSpeed, 0.5, (value) => {
settings.rainSpeed = value; saveSettings();
}));
atmosphericGroup.appendChild(createSliderRow(' Длина капель:', 'rainDropLengthSlider', 5, 30, settings.rainDropLength, 1, (value) => {
settings.rainDropLength = value; saveSettings();
}));
const cloudsRow = document.createElement('div');
cloudsRow.className = 'checkbox-row';
const cloudsLabel = document.createElement('label');
cloudsLabel.htmlFor = 'cloudsEnabledCheckbox';
cloudsLabel.textContent = 'Облака:';
const cloudsCheckbox = document.createElement('input');
cloudsCheckbox.type = 'checkbox';
cloudsCheckbox.id = 'cloudsEnabledCheckbox';
cloudsCheckbox.checked = settings.cloudsEnabled;
cloudsCheckbox.addEventListener('change', (e) => {
settings.cloudsEnabled = e.target.checked;
createClouds();
if ((settings.density > 0 || settings.rainEnabled || settings.cloudsEnabled) && !animationFrameId && settings.effectEnabled) startAnimation();
else if (!settings.density && !settings.rainEnabled && !settings.cloudsEnabled && animationFrameId) stopAnimation();
saveSettings();
});
cloudsRow.appendChild(cloudsCheckbox);
cloudsRow.appendChild(cloudsLabel);
atmosphericGroup.appendChild(cloudsRow);
atmosphericGroup.appendChild(createSliderRow(' Плотность облаков:', 'cloudDensitySlider', 0, 50, settings.cloudDensity, 1, (value) => {
settings.cloudDensity = value; createClouds(); saveSettings();
}));
atmosphericGroup.appendChild(createSliderRow(' Скорость облаков:', 'cloudSpeedSlider', 0.1, 2, settings.cloudSpeed, 0.1, (value) => {
settings.cloudSpeed = value; clouds.forEach(c => c.drift = settings.cloudSpeed + Math.random() * 0.1); saveSettings();
}));
atmosphericGroup.appendChild(createColorInputRow(' Цвет облаков:', 'cloudColorInput', settings.cloudColor, (value) => {
settings.cloudColor = value; saveSettings();
}));
controlPanel.appendChild(atmosphericGroup);
// --- Группа "Свечение" ---
const glowGroup = document.createElement('div');
glowGroup.className = 'effect-group';
const glowTitle = document.createElement('div');
glowTitle.className = 'effect-title';
glowTitle.textContent = 'Свечение';
glowGroup.appendChild(glowTitle);
const rgbEnableRow = document.createElement('div');
rgbEnableRow.className = 'checkbox-row';
const rgbEnableLabel = document.createElement('label');
rgbEnableLabel.htmlFor = 'rgbEnabledCheckbox';
rgbEnableLabel.textContent = 'RGB свечение:';
const rgbEnableCheckbox = document.createElement('input');
rgbEnableCheckbox.type = 'checkbox';
rgbEnableCheckbox.id = 'rgbEnabledCheckbox';
rgbEnableCheckbox.checked = settings.rgbEnabled;
rgbEnableCheckbox.addEventListener('change', (e) => {
settings.rgbEnabled = e.target.checked;
updateGlowControlsState();
saveSettings();
});
rgbEnableRow.appendChild(rgbEnableCheckbox);
rgbEnableRow.appendChild(rgbEnableLabel);
glowGroup.appendChild(rgbEnableRow);
const glowEnableRow = document.createElement('div');
glowEnableRow.className = 'checkbox-row';
const glowEnableLabel = document.createElement('label');
glowEnableLabel.htmlFor = 'glowCheckbox';
glowEnableLabel.textContent = 'Обычное свечение:';
const glowEnableCheckbox = document.createElement('input');
glowEnableCheckbox.type = 'checkbox';
glowEnableCheckbox.id = 'glowCheckbox';
glowEnableCheckbox.checked = settings.glowEnabled;
glowEnableCheckbox.addEventListener('change', (e) => {
settings.glowEnabled = e.target.checked;
updateGlowControlsState();
saveSettings();
});
glowEnableRow.appendChild(glowEnableCheckbox);
glowEnableRow.appendChild(glowEnableLabel);
glowGroup.appendChild(glowEnableRow);
glowGroup.appendChild(createSliderRow('Интенсивность:', 'glowIntensitySlider', 0, 20, settings.glowIntensity, 1, (value) => {
settings.glowIntensity = value; saveSettings();
}));
glowGroup.appendChild(createColorInputRow('Цвета RGB (через запятую):', 'rgbColorsInput', settings.glowColors.join(','), (value) => {
settings.glowColors = value.split(',').map(s => s.trim());
saveSettings();
}));
glowGroup.appendChild(createSliderRow('Скорость RGB:', 'rgbSpeedSlider', 0.1, 5, settings.rgbSpeed, 0.1, (value) => {
settings.rgbSpeed = value; saveSettings();
}));
controlPanel.appendChild(glowGroup);
// Автор
const authorDiv = document.createElement('div');
authorDiv.style.marginTop = '15px';
authorDiv.style.fontSize = '11px';
authorDiv.style.color = 'rgba(255, 255, 255, 0.7)';
authorDiv.textContent = 'Автор: Тех. [06] M. Ageev';
controlPanel.appendChild(authorDiv);
document.body.appendChild(controlPanel);
updateControlsState();
updateGlowControlsState();
}
function updateGlowControlsState() {
const glowCheckbox = document.getElementById('glowCheckbox');
const rgbCheckbox = document.getElementById('rgbEnabledCheckbox');
const glowIntensitySlider = document.getElementById('glowIntensitySlider');
const rgbColorsInput = document.getElementById('rgbColorsInput');
const rgbSpeedSlider = document.getElementById('rgbSpeedSlider');
if (glowCheckbox && rgbCheckbox && glowIntensitySlider && rgbColorsInput && rgbSpeedSlider) {
const isRgbEnabled = settings.rgbEnabled;
const isGlowEnabled = settings.glowEnabled;
glowCheckbox.disabled = isRgbEnabled;
glowIntensitySlider.disabled = isRgbEnabled && !isGlowEnabled;
rgbCheckbox.disabled = isGlowEnabled;
rgbColorsInput.disabled = !isRgbEnabled;
rgbSpeedSlider.disabled = !isRgbEnabled;
glowCheckbox.style.opacity = isRgbEnabled ? '0.5' : '1';
glowIntensitySlider.style.opacity = (isRgbEnabled && !isGlowEnabled) ? '0.5' : '1';
rgbCheckbox.style.opacity = isGlowEnabled ? '0.5' : '1';
rgbColorsInput.style.opacity = !isRgbEnabled ? '0.5' : '1';
rgbSpeedSlider.style.opacity = !isRgbEnabled ? '0.5' : '1';
glowCheckbox.style.cursor = isRgbEnabled ? 'not-allowed' : 'pointer';
glowIntensitySlider.style.cursor = (isRgbEnabled && !isGlowEnabled) ? 'not-allowed' : 'pointer';
rgbCheckbox.style.cursor = isGlowEnabled ? 'not-allowed' : 'pointer';
rgbColorsInput.style.cursor = isRgbEnabled ? 'pointer' : 'not-allowed';
rgbSpeedSlider.style.cursor = isRgbEnabled ? 'pointer' : 'not-allowed';
}
}
// Вспомогательные функции
function createSliderRow(labelText, id, min, max, value, step, onChange) {
const row = document.createElement('div');
row.className = 'control-row';
const label = document.createElement('label');
label.htmlFor = id;
label.textContent = labelText;
const slider = document.createElement('input');
slider.type = 'range';
slider.id = id;
slider.min = min.toString();
slider.max = max.toString();
slider.step = step.toString();
slider.value = value.toString();
const valueDisplay = document.createElement('span');
valueDisplay.className = 'value-display';
const displayValue = Number.isInteger(step) || step >= 1 ? Math.round(value).toString() : parseFloat(value).toFixed(1);
valueDisplay.textContent = displayValue;
slider.addEventListener('input', (e) => {
const newValue = parseFloat(e.target.value);
const displayValue = Number.isInteger(step) || step >= 1 ? Math.round(newValue).toString() : newValue.toFixed(1);
valueDisplay.textContent = displayValue;
onChange(newValue);
});
row.appendChild(label);
row.appendChild(slider);
row.appendChild(valueDisplay);
return row;
}
function createColorInputRow(labelText, id, value, onChange) {
const row = document.createElement('div');
row.className = 'color-input-row';
const label = document.createElement('label');
label.htmlFor = id;
label.textContent = labelText;
const input = document.createElement('input');
input.type = 'text';
input.id = id;
input.className = 'color-input';
input.value = value;
input.addEventListener('change', (e) => onChange(e.target.value));
row.appendChild(label);
row.appendChild(input);
return row;
}
function toggleControlPanel() {
settings.panelVisible = !settings.panelVisible;
if (controlPanel) {
requestAnimationFrame(() => {
if (settings.panelVisible) {
controlPanel.style.opacity = '1';
controlPanel.style.visibility = 'visible';
} else {
controlPanel.style.opacity = '0';
controlPanel.style.visibility = 'hidden';
}
});
}
saveSettings();
}
function updateControlsState() {
if (!controlPanel) return;
const controlsToDisable = controlPanel.querySelectorAll('input:not(#effectEnabledCheckbox), fieldset, button');
controlsToDisable.forEach(control => {
control.disabled = !settings.effectEnabled;
control.style.opacity = settings.effectEnabled ? '1' : '0.5';
control.style.cursor = settings.effectEnabled ? '' : 'not-allowed';
if (control.tagName === 'FIELDSET') {
const innerControls = control.querySelectorAll('input, label');
innerControls.forEach(inner => {
inner.style.opacity = settings.effectEnabled ? '1' : '0.6';
inner.style.cursor = settings.effectEnabled ? 'pointer' : 'not-allowed';
});
}
});
}
// --- ИНИЦИАЛИЗАЦИЯ ---
async function init() {
console.log("Falling Leaves Script Initializing (v2.5)...");
await loadSettings();
setupCanvas();
createControlPanel();
createSettingsButton();
if (settings.effectEnabled) {
startAnimation();
}
console.log("Falling Leaves Script Ready!");
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();