您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds persistent falling particles (leaves, petals, rain) with advanced controls (wind, glow, fog, selection, RGB) and realistic clouds to forum.blackrussia.online
// ==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(); } })();