| <!DOCTYPE html> |
| <html lang="ru"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Резонатор Сознания 3.0 | Цифровая Революция</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;500;700&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --neon-cyan: #00f7ff; |
| --neon-pink: #ff00f7; |
| --neon-yellow: #f7ff00; |
| --matrix-green: #00ff41; |
| --dark-bg: #050505; |
| --hologram-blue: rgba(0, 183, 255, 0.2); |
| } |
| |
| body { |
| font-family: 'Rajdhani', sans-serif; |
| background-color: var(--dark-bg); |
| color: white; |
| overflow: hidden; |
| touch-action: manipulation; |
| background-image: |
| radial-gradient(circle at 20% 30%, var(--hologram-blue) 0%, transparent 20%), |
| radial-gradient(circle at 80% 70%, rgba(255, 0, 183, 0.1) 0%, transparent 20%); |
| } |
| |
| .cyber-title { |
| font-family: 'Orbitron', sans-serif; |
| text-shadow: 0 0 15px var(--neon-cyan); |
| letter-spacing: 2px; |
| background: linear-gradient(90deg, var(--neon-cyan), var(--neon-pink)); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| .cyber-card { |
| background: rgba(10, 10, 20, 0.7); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(0, 247, 255, 0.3); |
| box-shadow: |
| 0 0 15px rgba(0, 247, 255, 0.3), |
| inset 0 0 10px rgba(0, 183, 255, 0.2); |
| border-radius: 8px; |
| transition: all 0.3s ease; |
| } |
| |
| .cyber-card:hover { |
| box-shadow: |
| 0 0 25px rgba(0, 247, 255, 0.5), |
| inset 0 0 15px rgba(0, 183, 255, 0.3); |
| transform: translateY(-3px); |
| } |
| |
| .cyber-btn { |
| position: relative; |
| overflow: hidden; |
| border: none; |
| background: linear-gradient(135deg, rgba(0, 247, 255, 0.2), rgba(255, 0, 183, 0.2)); |
| color: white; |
| font-weight: bold; |
| letter-spacing: 1px; |
| transition: all 0.3s ease; |
| box-shadow: 0 0 10px rgba(0, 247, 255, 0.3); |
| } |
| |
| .cyber-btn:hover { |
| box-shadow: 0 0 20px rgba(0, 247, 255, 0.5); |
| transform: translateY(-2px); |
| } |
| |
| .cyber-btn:active { |
| transform: translateY(1px); |
| } |
| |
| .cyber-btn::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: -100%; |
| width: 100%; |
| height: 100%; |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); |
| transition: all 0.7s ease; |
| } |
| |
| .cyber-btn:hover::before { |
| left: 100%; |
| } |
| |
| .cyber-slider::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 18px; |
| height: 18px; |
| border-radius: 50%; |
| background: var(--neon-cyan); |
| cursor: pointer; |
| box-shadow: 0 0 10px var(--neon-cyan); |
| border: 2px solid var(--dark-bg); |
| } |
| |
| .cyber-visualizer { |
| position: relative; |
| background: |
| radial-gradient(circle at center, rgba(0, 183, 255, 0.05) 0%, transparent 70%), |
| linear-gradient(to bottom, rgba(255, 0, 183, 0.02) 0%, transparent 100%); |
| } |
| |
| .cyber-particle { |
| position: absolute; |
| border-radius: 50%; |
| pointer-events: none; |
| transform: translate(-50%, -50%); |
| mix-blend-mode: screen; |
| } |
| |
| @keyframes cyber-pulse { |
| 0% { opacity: 0.3; transform: scale(0.95); } |
| 50% { opacity: 1; transform: scale(1.05); } |
| 100% { opacity: 0.3; transform: scale(0.95); } |
| } |
| |
| .cyber-pulse { |
| animation: cyber-pulse 2s infinite ease-in-out; |
| } |
| |
| .cyber-active { |
| box-shadow: 0 0 25px var(--neon-cyan) !important; |
| border-color: var(--neon-cyan) !important; |
| } |
| |
| .cyber-nav-portal { |
| position: relative; |
| width: 60px; |
| height: 60px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| overflow: hidden; |
| } |
| |
| .cyber-nav-portal::before { |
| content: ''; |
| position: absolute; |
| top: -50%; |
| left: -50%; |
| width: 200%; |
| height: 200%; |
| background: conic-gradient( |
| transparent, |
| var(--neon-cyan), |
| var(--neon-pink), |
| transparent |
| ); |
| animation: rotate 3s linear infinite; |
| } |
| |
| @keyframes rotate { |
| from { transform: rotate(0deg); } |
| to { transform: rotate(360deg); } |
| } |
| |
| .cyber-nav-portal-inner { |
| position: relative; |
| z-index: 2; |
| width: 56px; |
| height: 56px; |
| border-radius: 50%; |
| background: var(--dark-bg); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: bold; |
| font-size: 1.5rem; |
| color: var(--neon-cyan); |
| } |
| |
| .cyber-tooltip { |
| position: fixed; |
| bottom: 5rem; |
| left: 50%; |
| transform: translateX(-50%); |
| background: rgba(0, 15, 20, 0.9); |
| border: 1px solid var(--neon-cyan); |
| color: var(--neon-cyan); |
| padding: 0.5rem 1rem; |
| border-radius: 4px; |
| font-size: 0.9rem; |
| box-shadow: 0 0 15px rgba(0, 247, 255, 0.5); |
| z-index: 100; |
| opacity: 1; |
| transition: opacity 0.5s ease; |
| } |
| |
| .cyber-tooltip.hidden { |
| opacity: 0; |
| } |
| |
| .cyber-grid { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 0.75rem; |
| } |
| |
| .cyber-mode-selector { |
| padding: 0.75rem; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| border: 1px solid rgba(0, 247, 255, 0.3); |
| background: rgba(10, 20, 30, 0.5); |
| } |
| |
| .cyber-mode-selector:hover { |
| transform: scale(1.03); |
| box-shadow: 0 0 15px rgba(0, 247, 255, 0.3); |
| } |
| |
| .cyber-mode-selector.active { |
| transform: scale(1.05); |
| box-shadow: 0 0 20px var(--neon-cyan); |
| border-color: var(--neon-cyan); |
| background: rgba(0, 247, 255, 0.1); |
| } |
| |
| .cyber-matrix-text { |
| color: var(--matrix-green); |
| text-shadow: 0 0 5px var(--matrix-green); |
| font-family: 'Courier New', monospace; |
| } |
| |
| .cyber-status-indicator { |
| width: 12px; |
| height: 12px; |
| border-radius: 50%; |
| display: inline-block; |
| margin-right: 0.5rem; |
| } |
| |
| .cyber-status-active { |
| background-color: var(--matrix-green); |
| box-shadow: 0 0 10px var(--matrix-green); |
| } |
| |
| .cyber-status-inactive { |
| background-color: #ff0033; |
| box-shadow: 0 0 10px #ff0033; |
| } |
| |
| .cyber-audio-modal { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: rgba(5, 5, 15, 0.95); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| z-index: 1000; |
| } |
| |
| .cyber-audio-modal-content { |
| max-width: 400px; |
| text-align: center; |
| border: 2px solid var(--neon-cyan); |
| box-shadow: 0 0 30px var(--neon-cyan); |
| } |
| |
| .cyber-canvas { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| } |
| |
| .cyber-mode-visual { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| } |
| |
| .cyber-ritual-stones { |
| position: absolute; |
| bottom: 5%; |
| left: 50%; |
| transform: translateX(-50%); |
| display: flex; |
| gap: 2rem; |
| } |
| |
| .cyber-stone { |
| background: rgba(0, 247, 255, 0.1); |
| border: 1px solid var(--neon-cyan); |
| box-shadow: 0 0 10px var(--neon-cyan); |
| } |
| |
| @keyframes cyber-stone-pulse { |
| 0% { opacity: 0.3; box-shadow: 0 0 5px var(--neon-cyan); } |
| 50% { opacity: 1; box-shadow: 0 0 20px var(--neon-cyan); } |
| 100% { opacity: 0.3; box-shadow: 0 0 5px var(--neon-cyan); } |
| } |
| |
| .cyber-stone-pulse { |
| animation: cyber-stone-pulse 3s infinite ease-in-out; |
| } |
| |
| .cyber-voice-active { |
| background: linear-gradient(135deg, rgba(255, 0, 183, 0.3), rgba(0, 247, 255, 0.3)) !important; |
| box-shadow: 0 0 20px var(--neon-pink) !important; |
| } |
| </style> |
| </head> |
| <body class="h-screen flex flex-col"> |
| |
| <div id="audio-context-modal" class="cyber-audio-modal"> |
| <div class="cyber-audio-modal-content p-8 cyber-card"> |
| <h2 class="cyber-title text-3xl mb-4">Резонатор Сознания 3.0</h2> |
| <p class="mb-6 text-gray-300">Для активации цифрового резонатора требуется инициализация аудио-движка.</p> |
| <button id="init-audio" class="cyber-btn px-6 py-3 rounded-lg"> |
| АКТИВИРОВАТЬ СИСТЕМУ |
| </button> |
| </div> |
| </div> |
|
|
| |
| <header class="py-4 px-6 flex justify-between items-center border-b border-gray-800"> |
| <h1 class="cyber-title text-2xl md:text-3xl">Резонатор Сознания <span class="text-white">3.0</span></h1> |
| <div class="flex space-x-3"> |
| <button id="voice-control" class="cyber-btn px-4 py-2 rounded-lg text-sm"> |
| ГОЛОСОВОЕ УПРАВЛЕНИЕ |
| </button> |
| <button id="help-btn" class="cyber-btn px-4 py-2 rounded-lg text-sm"> |
| СПРАВКА |
| </button> |
| </div> |
| </header> |
|
|
| <main class="flex-1 flex flex-col md:flex-row overflow-hidden"> |
| |
| <div class="w-full md:w-1/3 p-4 cyber-card m-2"> |
| |
| <div class="flex justify-between items-center mb-6"> |
| <button id="prev-mode" class="cyber-nav-portal"> |
| <div class="cyber-nav-portal-inner">←</div> |
| </button> |
| |
| <div id="current-mode" class="px-4 py-2 rounded-lg bg-gray-900 text-center text-neon-cyan font-bold text-lg cyber-matrix-text"> |
| КРИСТАЛЛИЧЕСКИЙ РЕЗОНАНС |
| </div> |
| |
| <button id="next-mode" class="cyber-nav-portal"> |
| <div class="cyber-nav-portal-inner">→</div> |
| </button> |
| </div> |
| |
| |
| <div class="cyber-grid mb-6"> |
| <div data-mode="crystal" class="cyber-mode-selector active"> |
| <h3 class="font-bold mb-1 text-neon-cyan">КРИСТАЛЛИЧЕСКИЙ</h3> |
| <p class="text-xs text-gray-400">Статика и плавные огибающие</p> |
| </div> |
| <div data-mode="granular" class="cyber-mode-selector"> |
| <h3 class="font-bold mb-1 text-neon-pink">ГРАНУЛЯРНЫЙ</h3> |
| <p class="text-xs text-gray-400">Звуковые гранулы и фракталы</p> |
| </div> |
| <div data-mode="flow3d" class="cyber-mode-selector"> |
| <h3 class="font-bold mb-1 text-neon-yellow">3D-ПОТОК</h3> |
| <p class="text-xs text-gray-400">Пространственное позиционирование</p> |
| </div> |
| <div data-mode="ritual" class="cyber-mode-selector"> |
| <h3 class="font-bold mb-1 text-neon-cyan">РИТУАЛ МЕГАЛИТОВ</h3> |
| <p class="text-xs text-gray-400">Низкочастотные удары</p> |
| </div> |
| </div> |
| |
| |
| <div class="space-y-4 mb-6"> |
| <div class="space-y-1"> |
| <label class="block text-neon-cyan text-sm">ГРОМКОСТЬ</label> |
| <input type="range" id="volume" min="0" max="1" step="0.01" value="0.5" class="w-full cyber-slider"> |
| </div> |
| |
| <div class="space-y-1"> |
| <label class="block text-neon-pink text-sm">ТЕМБР</label> |
| <input type="range" id="timbre" min="0" max="1" step="0.01" value="0.5" class="w-full cyber-slider"> |
| </div> |
| |
| <div class="space-y-1"> |
| <label class="block text-neon-yellow text-sm">СКОРОСТЬ ГРАНУЛЯЦИИ</label> |
| <input type="range" id="granulation" min="0" max="1" step="0.01" value="0.3" class="w-full cyber-slider"> |
| </div> |
| |
| <div class="space-y-1"> |
| <label class="block text-neon-cyan text-sm">ГЛУБИНА РЕВЕРБЕРАЦИИ</label> |
| <input type="range" id="reverb" min="0" max="1" step="0.01" value="0.4" class="w-full cyber-slider"> |
| </div> |
| </div> |
| |
| |
| <div class="flex justify-between space-x-3"> |
| <button id="start-btn" class="cyber-btn flex-1 py-3 rounded-lg text-neon-cyan"> |
| СТАРТ |
| </button> |
| <button id="pause-btn" disabled class="cyber-btn flex-1 py-3 rounded-lg text-neon-yellow opacity-50"> |
| ПАУЗА |
| </button> |
| <button id="stop-btn" disabled class="cyber-btn flex-1 py-3 rounded-lg text-neon-pink opacity-50"> |
| СТОП |
| </button> |
| </div> |
| </div> |
| |
| |
| <div id="visualizer" class="flex-1 cyber-visualizer relative m-2 cyber-card"> |
| <canvas id="oscilloscope" class="cyber-canvas"></canvas> |
| <div id="particles-container" class="cyber-canvas"></div> |
| |
| |
| <div id="crystal-mandala" class="cyber-mode-visual w-64 h-64"> |
| <div class="absolute inset-0 rounded-full border border-neon-cyan opacity-30 cyber-pulse" style="border-color: var(--neon-cyan);"></div> |
| <div class="absolute inset-0 rounded-full border border-neon-pink opacity-30 cyber-pulse" style="border-color: var(--neon-pink); transform: rotate(30deg); animation-delay: 0.5s;"></div> |
| <div class="absolute inset-0 rounded-full border border-neon-yellow opacity-30 cyber-pulse" style="border-color: var(--neon-yellow); transform: rotate(60deg); animation-delay: 1s;"></div> |
| </div> |
| |
| <div id="granular-mandala" class="cyber-mode-visual hidden w-64 h-64"> |
| |
| </div> |
| |
| <div id="flow3d-container" class="cyber-mode-visual hidden w-full h-full"> |
| |
| </div> |
| |
| <div id="ritual-container" class="cyber-mode-visual hidden w-full h-full"> |
| <div class="cyber-ritual-stones"> |
| <div class="cyber-stone w-16 h-24 cyber-stone-pulse" style="animation-delay: 0s;"></div> |
| <div class="cyber-stone w-16 h-32 cyber-stone-pulse" style="animation-delay: 0.5s;"></div> |
| <div class="cyber-stone w-16 h-40 cyber-stone-pulse" style="animation-delay: 1s;"></div> |
| <div class="cyber-stone w-16 h-32 cyber-stone-pulse" style="animation-delay: 1.5s;"></div> |
| <div class="cyber-stone w-16 h-24 cyber-stone-pulse" style="animation-delay: 2s;"></div> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| |
| <footer class="py-2 px-4 flex justify-between items-center text-xs bg-gray-900 text-gray-400 border-t border-gray-800 cyber-matrix-text"> |
| <div id="status" class="flex items-center"> |
| <div class="cyber-status-indicator cyber-status-inactive"></div> |
| <span>СИСТЕМА НЕ АКТИВНА</span> |
| </div> |
| <div id="frequency-display" class="text-neon-cyan"> |
| ЧАСТОТА: -- Гц |
| </div> |
| <div> |
| <span id="current-time">00:00</span> / <span id="session-time">30:00</span> |
| </div> |
| </footer> |
|
|
| |
| <div id="tooltip" class="cyber-tooltip hidden"></div> |
|
|
| <script> |
| |
| const state = { |
| audioInitialized: false, |
| currentMode: 'crystal', |
| modes: ['crystal', 'granular', 'flow3d', 'ritual'], |
| modeNames: { |
| crystal: 'КРИСТАЛЛИЧЕСКИЙ РЕЗОНАНС', |
| granular: 'ГРАНУЛЯРНАЯ МАНДАЛА', |
| flow3d: '3D-ПОТОК', |
| ritual: 'РИТУАЛ МЕГАЛИТОВ' |
| }, |
| isPlaying: false, |
| isPaused: false, |
| startTime: 0, |
| elapsedTime: 0, |
| sessionDuration: 1800, |
| audioContext: null, |
| analyser: null, |
| oscillator: null, |
| gainNode: null, |
| biquadFilter: null, |
| reverbNode: null, |
| delayNode: null, |
| granulator: null, |
| particles: [], |
| lastTouch: { x: 0, y: 0 }, |
| canvas: null, |
| canvasCtx: null, |
| animationFrame: null, |
| voiceActive: false |
| }; |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| initCanvas(); |
| |
| |
| document.getElementById('init-audio').addEventListener('click', initAudioContext); |
| document.getElementById('start-btn').addEventListener('click', startSession); |
| document.getElementById('pause-btn').addEventListener('click', togglePause); |
| document.getElementById('stop-btn').addEventListener('click', stopSession); |
| document.getElementById('prev-mode').addEventListener('click', prevMode); |
| document.getElementById('next-mode').addEventListener('click', nextMode); |
| document.getElementById('voice-control').addEventListener('click', initVoiceControl); |
| document.getElementById('help-btn').addEventListener('click', showHelp); |
| |
| |
| document.querySelectorAll('.cyber-mode-selector').forEach(el => { |
| el.addEventListener('click', () => { |
| const mode = el.getAttribute('data-mode'); |
| setMode(mode); |
| }); |
| }); |
| |
| |
| document.getElementById('volume').addEventListener('input', updateVolume); |
| document.getElementById('timbre').addEventListener('input', updateTimbre); |
| document.getElementById('granulation').addEventListener('input', updateGranulation); |
| document.getElementById('reverb').addEventListener('input', updateReverb); |
| |
| |
| document.getElementById('visualizer').addEventListener('click', handleVisualizerClick); |
| document.getElementById('visualizer').addEventListener('touchstart', handleVisualizerTouch); |
| |
| |
| setInterval(updateTimeDisplay, 1000); |
| |
| |
| animateBackground(); |
| }); |
| |
| |
| function animateBackground() { |
| const bg = document.body; |
| let hue = 0; |
| |
| setInterval(() => { |
| hue = (hue + 0.5) % 360; |
| bg.style.backgroundImage = ` |
| radial-gradient(circle at 20% 30%, hsla(${hue}, 100%, 50%, 0.1) 0%, transparent 20%), |
| radial-gradient(circle at 80% 70%, hsla(${(hue + 120) % 360}, 100%, 50%, 0.1) 0%, transparent 20%) |
| `; |
| }, 50); |
| } |
| |
| |
| function initAudioContext() { |
| if (state.audioInitialized) return; |
| |
| try { |
| |
| state.audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| |
| |
| state.analyser = state.audioContext.createAnalyser(); |
| state.analyser.fftSize = 256; |
| |
| |
| state.gainNode = state.audioContext.createGain(); |
| state.biquadFilter = state.audioContext.createBiquadFilter(); |
| state.reverbNode = state.audioContext.createConvolver(); |
| state.delayNode = state.audioContext.createDelay(); |
| |
| |
| state.granulator = { |
| active: false, |
| grainSize: 0.1, |
| overlap: 0.05, |
| position: 0, |
| buffer: null |
| }; |
| |
| |
| state.gainNode.connect(state.biquadFilter); |
| state.biquadFilter.connect(state.analyser); |
| state.analyser.connect(state.audioContext.destination); |
| |
| |
| state.gainNode.gain.value = 0.5; |
| state.biquadFilter.frequency.value = 800; |
| |
| |
| state.audioInitialized = true; |
| document.getElementById('audio-context-modal').style.display = 'none'; |
| document.getElementById('status').innerHTML = ` |
| <div class="cyber-status-indicator cyber-status-active"></div> |
| <span>СИСТЕМА АКТИВИРОВАНА</span> |
| `; |
| |
| |
| visualize(); |
| |
| |
| showTooltip('АУДИО СИСТЕМА АКТИВИРОВАНА. ГОТОВО К ЦИФРОВОМУ РЕЗОНАНСУ.'); |
| } catch (e) { |
| console.error('Ошибка инициализации аудио:', e); |
| showTooltip('ОШИБКА: НЕ УДАЛОСЬ АКТИВИРОВАТЬ АУДИО СИСТЕМУ'); |
| } |
| } |
| |
| |
| function initCanvas() { |
| state.canvas = document.getElementById('oscilloscope'); |
| state.canvas.width = state.canvas.offsetWidth; |
| state.canvas.height = state.canvas.offsetHeight; |
| state.canvasCtx = state.canvas.getContext('2d'); |
| } |
| |
| |
| function visualize() { |
| if (!state.audioInitialized) return; |
| |
| state.animationFrame = requestAnimationFrame(visualize); |
| |
| const width = state.canvas.width; |
| const height = state.canvas.height; |
| const analyser = state.analyser; |
| const bufferLength = analyser.frequencyBinCount; |
| const dataArray = new Uint8Array(bufferLength); |
| |
| |
| state.canvasCtx.fillStyle = 'rgba(5, 5, 15, 0.2)'; |
| state.canvasCtx.fillRect(0, 0, width, height); |
| |
| |
| analyser.getByteFrequencyData(dataArray); |
| |
| |
| state.canvasCtx.lineWidth = 2; |
| |
| |
| let lineGradient; |
| switch(state.currentMode) { |
| case 'crystal': |
| lineGradient = state.canvasCtx.createLinearGradient(0, 0, width, 0); |
| lineGradient.addColorStop(0, 'rgba(0, 247, 255, 0.8)'); |
| lineGradient.addColorStop(1, 'rgba(255, 0, 247, 0.8)'); |
| break; |
| case 'granular': |
| lineGradient = state.canvasCtx.createLinearGradient(0, 0, width, 0); |
| lineGradient.addColorStop(0, 'rgba(255, 0, 247, 0.8)'); |
| lineGradient.addColorStop(1, 'rgba(255, 247, 0, 0.8)'); |
| break; |
| case 'flow3d': |
| lineGradient = state.canvasCtx.createLinearGradient(0, 0, width, 0); |
| lineGradient.addColorStop(0, 'rgba(255, 247, 0, 0.8)'); |
| lineGradient.addColorStop(1, 'rgba(0, 247, 255, 0.8)'); |
| break; |
| case 'ritual': |
| lineGradient = state.canvasCtx.createLinearGradient(0, 0, width, 0); |
| lineGradient.addColorStop(0, 'rgba(0, 247, 255, 0.8)'); |
| lineGradient.addColorStop(1, 'rgba(0, 255, 41, 0.8)'); |
| break; |
| } |
| |
| state.canvasCtx.strokeStyle = lineGradient; |
| state.canvasCtx.beginPath(); |
| |
| const sliceWidth = width * 1.0 / bufferLength; |
| let x = 0; |
| |
| for (let i = 0; i < bufferLength; i++) { |
| const v = dataArray[i] / 128.0; |
| const y = v * height / 2; |
| |
| if (i === 0) { |
| state.canvasCtx.moveTo(x, y); |
| } else { |
| state.canvasCtx.lineTo(x, y); |
| } |
| |
| x += sliceWidth; |
| } |
| |
| state.canvasCtx.lineTo(width, height / 2); |
| state.canvasCtx.stroke(); |
| |
| |
| updateFrequencyDisplay(); |
| |
| |
| updateParticles(); |
| } |
| |
| |
| function updateFrequencyDisplay() { |
| if (!state.audioInitialized || !state.oscillator) { |
| document.getElementById('frequency-display').textContent = 'ЧАСТОТА: -- Гц'; |
| return; |
| } |
| |
| const frequency = state.oscillator.frequency.value; |
| document.getElementById('frequency-display').textContent = `ЧАСТОТА: ${Math.round(frequency)} Гц`; |
| } |
| |
| |
| function updateParticles() { |
| const container = document.getElementById('particles-container'); |
| |
| |
| container.innerHTML = ''; |
| |
| if (!state.isPlaying) return; |
| |
| |
| const particleCount = state.currentMode === 'granular' ? 100 : |
| state.currentMode === 'flow3d' ? 50 : 30; |
| |
| for (let i = 0; i < particleCount; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'cyber-particle'; |
| |
| |
| if (state.currentMode === 'crystal') { |
| particle.style.width = `${Math.random() * 6 + 2}px`; |
| particle.style.height = particle.style.width; |
| particle.style.backgroundColor = i % 3 === 0 ? 'var(--neon-cyan)' : |
| i % 3 === 1 ? 'var(--neon-pink)' : 'var(--neon-yellow)'; |
| particle.style.left = `${Math.random() * 100}%`; |
| particle.style.top = `${Math.random() * 100}%`; |
| particle.style.opacity = Math.random() * 0.5 + 0.1; |
| |
| |
| particle.style.transition = `all ${Math.random() * 3 + 2}s linear`; |
| setTimeout(() => { |
| particle.style.left = `${Math.random() * 100}%`; |
| particle.style.top = `${Math.random() * 100}%`; |
| }, 100); |
| } |
| else if (state.currentMode === 'granular') { |
| particle.style.width = `${Math.random() * 10 + 5}px`; |
| particle.style.height = particle.style.width; |
| particle.style.backgroundColor = i % 2 === 0 ? 'var(--neon-pink)' : 'var(--neon-yellow)'; |
| particle.style.left = `${50 + Math.sin(i) * 40}%`; |
| particle.style.top = `${50 + Math.cos(i) * 40}%`; |
| particle.style.opacity = Math.random() * 0.7 + 0.3; |
| |
| |
| const angle = (i / particleCount) * Math.PI * 2; |
| const radius = Math.random() * 30 + 10; |
| const speed = 0.02 + i * 0.005; |
| |
| let currentAngle = angle; |
| const animate = () => { |
| if (!particle.parentElement) return; |
| |
| currentAngle += speed; |
| const x = 50 + Math.cos(currentAngle) * radius; |
| const y = 50 + Math.sin(currentAngle) * radius * 0.6; |
| |
| particle.style.left = `${x}%`; |
| particle.style.top = `${y}%`; |
| |
| requestAnimationFrame(animate); |
| }; |
| |
| animate(); |
| } |
| else if (state.currentMode === 'flow3d') { |
| particle.style.width = `${Math.random() * 8 + 4}px`; |
| particle.style.height = particle.style.width; |
| particle.style.backgroundColor = 'var(--neon-yellow)'; |
| particle.style.left = `${Math.random() * 100}%`; |
| particle.style.top = `${Math.random() * 100}%`; |
| particle.style.opacity = Math.random() * 0.5 + 0.2; |
| |
| |
| const depth = Math.random() * 100; |
| const startX = Math.random() * 100; |
| const speed = Math.random() * 0.5 + 0.2; |
| |
| let currentY = -10; |
| const animate = () => { |
| if (!particle.parentElement) return; |
| |
| currentY += speed; |
| if (currentY > 110) currentY = -10; |
| |
| const scale = 1 - depth / 200; |
| const x = startX + (depth / 10) * Math.sin(currentY / 20); |
| |
| particle.style.left = `${x}%`; |
| particle.style.top = `${currentY}%`; |
| particle.style.transform = `translate(-50%, -50%) scale(${scale})`; |
| particle.style.opacity = scale * 0.7; |
| |
| requestAnimationFrame(animate); |
| }; |
| |
| animate(); |
| } |
| else if (state.currentMode === 'ritual') { |
| particle.style.width = '2px'; |
| particle.style.height = `${Math.random() * 15 + 5}px`; |
| particle.style.backgroundColor = 'var(--neon-cyan)'; |
| particle.style.left = `${Math.random() * 100}%`; |
| particle.style.bottom = `${Math.random() * 30 + 10}%`; |
| particle.style.opacity = Math.random() * 0.3 + 0.1; |
| |
| |
| let scale = 1; |
| let direction = 1; |
| const animate = () => { |
| if (!particle.parentElement) return; |
| |
| scale += direction * 0.05; |
| if (scale > 1.5) direction = -1; |
| if (scale < 0.5) direction = 1; |
| |
| particle.style.transform = `translate(-50%, -50%) scaleY(${scale})`; |
| particle.style.opacity = (scale - 0.5) * 0.4; |
| |
| setTimeout(() => { |
| requestAnimationFrame(animate); |
| }, 50); |
| }; |
| |
| animate(); |
| } |
| |
| container.appendChild(particle); |
| } |
| } |
| |
| |
| function startSession() { |
| if (!state.audioInitialized) { |
| showTooltip('ОШИБКА: АУДИО СИСТЕМА НЕ АКТИВИРОВАНА'); |
| return; |
| } |
| |
| if (state.isPlaying) return; |
| |
| |
| state.oscillator = state.audioContext.createOscillator(); |
| |
| |
| switch (state.currentMode) { |
| case 'crystal': |
| state.oscillator.type = 'sine'; |
| state.oscillator.frequency.setValueAtTime(432, state.audioContext.currentTime); |
| state.biquadFilter.frequency.setValueAtTime(800, state.audioContext.currentTime); |
| break; |
| case 'granular': |
| state.oscillator.type = 'sawtooth'; |
| state.oscillator.frequency.setValueAtTime(220, state.audioContext.currentTime); |
| state.biquadFilter.frequency.setValueAtTime(1500, state.audioContext.currentTime); |
| state.granulator.active = true; |
| break; |
| case 'flow3d': |
| state.oscillator.type = 'triangle'; |
| state.oscillator.frequency.setValueAtTime(340, state.audioContext.currentTime); |
| state.biquadFilter.frequency.setValueAtTime(1200, state.audioContext.currentTime); |
| break; |
| case 'ritual': |
| state.oscillator.type = 'square'; |
| state.oscillator.frequency.setValueAtTime(55, state.audioContext.currentTime); |
| state.biquadFilter.frequency.setValueAtTime(200, state.audioContext.currentTime); |
| break; |
| } |
| |
| |
| state.oscillator.connect(state.gainNode); |
| |
| |
| state.oscillator.start(); |
| |
| |
| state.isPlaying = true; |
| state.isPaused = false; |
| state.startTime = Date.now() - (state.elapsedTime * 1000); |
| |
| |
| document.getElementById('start-btn').disabled = true; |
| document.getElementById('pause-btn').disabled = false; |
| document.getElementById('stop-btn').disabled = false; |
| document.getElementById('pause-btn').classList.remove('opacity-50'); |
| document.getElementById('stop-btn').classList.remove('opacity-50'); |
| |
| |
| document.querySelector(`.cyber-mode-selector[data-mode="${state.currentMode}"]`).classList.add('active'); |
| |
| |
| updateVisualizationForMode(); |
| |
| showTooltip(`СЕАНС "${state.modeNames[state.currentMode]}" АКТИВИРОВАН`); |
| } |
| |
| |
| function togglePause() { |
| if (!state.isPlaying) return; |
| |
| if (state.isPaused) { |
| |
| state.audioContext.resume(); |
| state.isPaused = false; |
| document.getElementById('pause-btn').textContent = 'ПАУЗА'; |
| showTooltip('СЕАНС ПРОДОЛЖЕН'); |
| } else { |
| |
| state.audioContext.suspend(); |
| state.isPaused = true; |
| document.getElementById('pause-btn').textContent = 'ПРОДОЛЖИТЬ'; |
| showTooltip('СЕАНС ПРИОСТАНОВЛЕН'); |
| } |
| } |
| |
| |
| function stopSession() { |
| if (!state.isPlaying) return; |
| |
| |
| if (state.oscillator) { |
| state.oscillator.stop(); |
| state.oscillator.disconnect(); |
| state.oscillator = null; |
| } |
| |
| |
| state.granulator.active = false; |
| |
| |
| state.isPlaying = false; |
| state.isPaused = false; |
| state.elapsedTime = 0; |
| |
| |
| document.getElementById('start-btn').disabled = false; |
| document.getElementById('pause-btn').disabled = true; |
| document.getElementById('stop-btn').disabled = true; |
| document.getElementById('pause-btn').classList.add('opacity-50'); |
| document.getElementById('stop-btn').classList.add('opacity-50'); |
| document.getElementById('pause-btn').textContent = 'ПАУЗА'; |
| |
| |
| updateVisualizationForMode(); |
| |
| showTooltip('СЕАНС ЗАВЕРШЕН'); |
| } |
| |
| |
| function updateVolume(e) { |
| if (!state.audioInitialized) return; |
| const value = parseFloat(e.target.value); |
| state.gainNode.gain.setValueAtTime(value, state.audioContext.currentTime); |
| } |
| |
| |
| function updateTimbre(e) { |
| if (!state.audioInitialized) return; |
| const value = parseFloat(e.target.value); |
| |
| |
| switch (state.currentMode) { |
| case 'crystal': |
| state.biquadFilter.frequency.setValueAtTime(300 + value * 1500, state.audioContext.currentTime); |
| break; |
| case 'granular': |
| state.biquadFilter.Q.setValueAtTime(value * 10, state.audioContext.currentTime); |
| break; |
| case 'flow3d': |
| state.biquadFilter.frequency.setValueAtTime(200 + value * 2000, state.audioContext.currentTime); |
| break; |
| case 'ritual': |
| state.biquadFilter.frequency.setValueAtTime(50 + value * 500, state.audioContext.currentTime); |
| break; |
| } |
| } |
| |
| |
| function updateGranulation(e) { |
| if (!state.audioInitialized) return; |
| const value = parseFloat(e.target.value); |
| |
| if (state.granulator.active) { |
| state.granulator.grainSize = 0.2 - (value * 0.15); |
| state.granulator.overlap = value * 0.1; |
| } |
| } |
| |
| |
| function updateReverb(e) { |
| if (!state.audioInitialized) return; |
| const value = parseFloat(e.target.value); |
| |
| |
| if (value > 0.1) { |
| if (!state.delayNode.connected) { |
| state.delayNode.connect(state.analyser); |
| } |
| state.delayNode.delayTime.setValueAtTime(value * 0.5, state.audioContext.currentTime); |
| } else { |
| if (state.delayNode.connected) { |
| state.delayNode.disconnect(); |
| } |
| } |
| } |
| |
| |
| function setMode(mode) { |
| if (!state.modes.includes(mode)) return; |
| |
| |
| state.currentMode = mode; |
| document.getElementById('current-mode').textContent = state.modeNames[mode]; |
| |
| |
| document.querySelectorAll('.cyber-mode-selector').forEach(el => { |
| el.classList.remove('active'); |
| }); |
| document.querySelector(`.cyber-mode-selector[data-mode="${mode}"]`).classList.add('active'); |
| |
| |
| if (state.isPlaying) { |
| stopSession(); |
| startSession(); |
| } else { |
| updateVisualizationForMode(); |
| } |
| |
| showTooltip(`РЕЖИМ ИЗМЕНЕН НА "${state.modeNames[mode]}"`); |
| } |
| |
| |
| function prevMode() { |
| const currentIndex = state.modes.indexOf(state.currentMode); |
| const prevIndex = (currentIndex - 1 + state.modes.length) % state.modes.length; |
| setMode(state.modes[prevIndex]); |
| } |
| |
| |
| function nextMode() { |
| const currentIndex = state.modes.indexOf(state.currentMode); |
| const nextIndex = (currentIndex + 1) % state.modes.length; |
| setMode(state.modes[nextIndex]); |
| } |
| |
| |
| function updateVisualizationForMode() { |
| |
| document.getElementById('crystal-mandala').classList.add('hidden'); |
| document.getElementById('granular-mandala').classList.add('hidden'); |
| document.getElementById('flow3d-container').classList.add('hidden'); |
| document.getElementById('ritual-container').classList.add('hidden'); |
| |
| |
| switch (state.currentMode) { |
| case 'crystal': |
| document.getElementById('crystal-mandala').classList.remove('hidden'); |
| break; |
| case 'granular': |
| document.getElementById('granular-mandala').classList.remove('hidden'); |
| initGranularMandala(); |
| break; |
| case 'flow3d': |
| document.getElementById('flow3d-container').classList.remove('hidden'); |
| initFlow3D(); |
| break; |
| case 'ritual': |
| document.getElementById('ritual-container').classList.remove('hidden'); |
| break; |
| } |
| } |
| |
| |
| function initGranularMandala() { |
| const container = document.getElementById('granular-mandala'); |
| container.innerHTML = ''; |
| |
| const levels = 5; |
| const elementsPerLevel = 12; |
| |
| for (let l = 0; l < levels; l++) { |
| const radius = 30 + l * 15; |
| const rotationSpeed = 0.5 + l * 0.2; |
| |
| for (let i = 0; i < elementsPerLevel; i++) { |
| const angle = (i / elementsPerLevel) * Math.PI * 2; |
| const element = document.createElement('div'); |
| element.className = 'absolute w-4 h-4 rounded-full'; |
| element.style.backgroundColor = l % 2 === 0 ? 'var(--neon-pink)' : 'var(--neon-yellow)'; |
| element.style.left = '50%'; |
| element.style.top = '50%'; |
| element.style.transform = `translate(-50%, -50%) rotate(${angle}rad) translate(${radius}px)`; |
| element.style.opacity = 0.7 - (l * 0.1); |
| |
| |
| let currentAngle = angle; |
| const animate = () => { |
| if (!element.parentElement) return; |
| |
| currentAngle += rotationSpeed * 0.01; |
| element.style.transform = `translate(-50%, -50%) rotate(${currentAngle}rad) translate(${radius}px)`; |
| |
| requestAnimationFrame(animate); |
| }; |
| |
| animate(); |
| |
| container.appendChild(element); |
| } |
| } |
| } |
| |
| |
| function initFlow3D() { |
| const container = document.getElementById('flow3d-container'); |
| container.innerHTML = ''; |
| |
| |
| const sphere = document.createElement('div'); |
| sphere.className = 'absolute w-16 h-16 rounded-full'; |
| sphere.style.backgroundColor = 'var(--neon-yellow)'; |
| sphere.style.left = '50%'; |
| sphere.style.top = '50%'; |
| sphere.style.transform = 'translate(-50%, -50%)'; |
| sphere.style.boxShadow = '0 0 20px var(--neon-yellow)'; |
| sphere.style.opacity = '0.7'; |
| |
| |
| let scale = 1; |
| let direction = 1; |
| const animateSphere = () => { |
| if (!sphere.parentElement) return; |
| |
| scale += direction * 0.01; |
| if (scale > 1.3) direction = -1; |
| if (scale < 0.7) direction = 1; |
| |
| sphere.style.transform = `translate(-50%, -50%) scale(${scale})`; |
| sphere.style.opacity = 0.5 + (scale - 0.7) * 0.5; |
| |
| requestAnimationFrame(animateSphere); |
| }; |
| |
| animateSphere(); |
| container.appendChild(sphere); |
| |
| |
| const orbitalCount = 8; |
| for (let i = 0; i < orbitalCount; i++) { |
| const orbital = document.createElement('div'); |
| orbital.className = 'absolute w-6 h-6 rounded-full'; |
| orbital.style.backgroundColor = i % 2 === 0 ? 'var(--neon-cyan)' : 'var(--neon-pink)'; |
| orbital.style.left = '50%'; |
| orbital.style.top = '50%'; |
| |
| const radius = 40 + i * 10; |
| const speed = 0.02 + i * 0.005; |
| let angle = (i / orbitalCount) * Math.PI * 2; |
| |
| const animateOrbital = () => { |
| if (!orbital.parentElement) return; |
| |
| angle += speed; |
| const x = Math.cos(angle) * radius; |
| const y = Math.sin(angle) * radius * 0.6; |
| const z = Math.sin(angle) * radius * 0.3; |
| |
| orbital.style.transform = `translate(-50%, -50%) translate3d(${x}px, ${y}px, ${z}px)`; |
| orbital.style.opacity = 0.5 + Math.sin(angle) * 0.3; |
| |
| requestAnimationFrame(animateOrbital); |
| }; |
| |
| animateOrbital(); |
| container.appendChild(orbital); |
| } |
| } |
| |
| |
| function handleVisualizerClick(e) { |
| if (!state.isPlaying) return; |
| |
| const rect = e.target.getBoundingClientRect(); |
| const x = e.clientX - rect.left; |
| const y = e.clientY - rect.top; |
| |
| |
| const normX = x / rect.width; |
| const normY = y / rect.height; |
| |
| |
| if (state.oscillator) { |
| let newFreq; |
| |
| switch (state.currentMode) { |
| case 'crystal': |
| newFreq = 200 + normX * 800; |
| break; |
| case 'granular': |
| newFreq = 100 + normX * 1000; |
| state.granulator.grainSize = 0.2 - normY * 0.15; |
| break; |
| case 'flow3d': |
| newFreq = 150 + (normX + normY) * 500; |
| break; |
| case 'ritual': |
| newFreq = 30 + normX * 100; |
| break; |
| default: |
| newFreq = 200 + normX * 800; |
| } |
| |
| state.oscillator.frequency.setValueAtTime(newFreq, state.audioContext.currentTime); |
| |
| |
| createClickParticles(x, y); |
| } |
| } |
| |
| |
| function handleVisualizerTouch(e) { |
| e.preventDefault(); |
| if (!state.isPlaying) return; |
| |
| const touch = e.touches[0]; |
| const rect = e.target.getBoundingClientRect(); |
| const x = touch.clientX - rect.left; |
| const y = touch.clientY - rect.top; |
| |
| state.lastTouch = { x, y }; |
| |
| |
| const normX = x / rect.width; |
| const normY = y / rect.height; |
| |
| |
| if (state.oscillator) { |
| let newFreq; |
| |
| switch (state.currentMode) { |
| case 'crystal': |
| newFreq = 200 + normX * 800; |
| break; |
| case 'granular': |
| newFreq = 100 + normX * 1000; |
| state.granulator.grainSize = 0.2 - normY * 0.15; |
| break; |
| case 'flow3d': |
| newFreq = 150 + (normX + normY) * 500; |
| break; |
| case 'ritual': |
| newFreq = 30 + normX * 100; |
| break; |
| default: |
| newFreq = 200 + normX * 800; |
| } |
| |
| state.oscillator.frequency.setValueAtTime(newFreq, state.audioContext.currentTime); |
| |
| |
| createClickParticles(x, y); |
| } |
| } |
| |
| |
| function createClickParticles(x, y) { |
| const container = document.getElementById('visualizer'); |
| const particleCount = 15; |
| |
| for (let i = 0; i < particleCount; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'cyber-particle absolute'; |
| |
| |
| if (state.currentMode === 'crystal') { |
| particle.style.width = `${Math.random() * 8 + 4}px`; |
| particle.style.height = particle.style.width; |
| particle.style.backgroundColor = i % 3 === 0 ? 'var(--neon-cyan)' : |
| i % 3 === 1 ? 'var(--neon-pink)' : 'var(--neon-yellow)'; |
| particle.style.left = `${x}px`; |
| particle.style.top = `${y}px`; |
| particle.style.opacity = '1'; |
| |
| |
| const angle = Math.random() * Math.PI * 2; |
| const distance = Math.random() * 50 + 20; |
| const duration = Math.random() * 1000 + 500; |
| |
| particle.style.transition = `all ${duration}ms ease-out`; |
| setTimeout(() => { |
| particle.style.left = `${x + Math.cos(angle) * distance}px`; |
| particle.style.top = `${y + Math.sin(angle) * distance}px`; |
| particle.style.opacity = '0'; |
| }, 10); |
| |
| |
| setTimeout(() => { |
| if (particle.parentElement) { |
| particle.remove(); |
| } |
| }, duration + 100); |
| } |
| else if (state.currentMode === 'granular') { |
| particle.style.width = `${Math.random() * 6 + 3}px`; |
| particle.style.height = particle.style.width; |
| particle.style.backgroundColor = i % 2 === 0 ? 'var(--neon-pink)' : 'var(--neon-yellow)'; |
| particle.style.left = `${x}px`; |
| particle.style.top = `${y}px`; |
| particle.style.opacity = '1'; |
| |
| |
| const angle = (i / particleCount) * Math.PI * 2; |
| const distance = Math.random() * 40 + 30; |
| const duration = Math.random() * 1500 + 500; |
| |
| particle.style.transition = `all ${duration}ms cubic-bezier(0.1, 0.8, 0.2, 1)`; |
| setTimeout(() => { |
| particle.style.left = `${x + Math.cos(angle) * distance}px`; |
| particle.style.top = `${y + Math.sin(angle) * distance}px`; |
| particle.style.opacity = '0'; |
| }, 10); |
| |
| |
| setTimeout(() => { |
| if (particle.parentElement) { |
| particle.remove(); |
| } |
| }, duration + 100); |
| } |
| else if (state.currentMode === 'flow3d') { |
| particle.style.width = `${Math.random() * 10 + 5}px`; |
| particle.style.height = '2px'; |
| particle.style.backgroundColor = 'var(--neon-yellow)'; |
| particle.style.left = `${x}px`; |
| particle.style.top = `${y}px`; |
| particle.style.opacity = '1'; |
| |
| |
| const angle = Math.random() * Math.PI * 2; |
| const distance = Math.random() * 100 + 50; |
| const duration = Math.random() * 2000 + 1000; |
| |
| particle.style.transition = `all ${duration}ms ease-out`; |
| setTimeout(() => { |
| particle.style.left = `${x + Math.cos(angle) * distance}px`; |
| particle.style.top = `${y + Math.sin(angle) * distance * 0.3}px`; |
| particle.style.opacity = '0'; |
| particle.style.transform = 'rotate(45deg)'; |
| }, 10); |
| |
| |
| setTimeout(() => { |
| if (particle.parentElement) { |
| particle.remove(); |
| } |
| }, duration + 100); |
| } |
| else if (state.currentMode === 'ritual') { |
| particle.style.width = '2px'; |
| particle.style.height = `${Math.random() * 30 + 10}px`; |
| particle.style.backgroundColor = 'var(--neon-cyan)'; |
| particle.style.left = `${x}px`; |
| particle.style.top = `${y}px`; |
| particle.style.opacity = '1'; |
| |
| |
| const duration = Math.random() * 1000 + 500; |
| |
| particle.style.transition = `all ${duration}ms ease-out`; |
| setTimeout(() => { |
| particle.style.height = '0'; |
| particle.style.opacity = '0'; |
| }, 10); |
| |
| |
| setTimeout(() => { |
| if (particle.parentElement) { |
| particle.remove(); |
| } |
| }, duration + 100); |
| } |
| |
| container.appendChild(particle); |
| } |
| } |
| |
| |
| function updateTimeDisplay() { |
| if (state.isPlaying && !state.isPaused) { |
| state.elapsedTime = Math.floor((Date.now() - state.startTime) / 1000); |
| } |
| |
| const currentMinutes = Math.floor(state.elapsedTime / 60); |
| const currentSeconds = state.elapsedTime % 60; |
| document.getElementById('current-time').textContent = |
| `${currentMinutes.toString().padStart(2, '0')}:${currentSeconds.toString().padStart(2, '0')}`; |
| |
| const sessionMinutes = Math.floor(state.sessionDuration / 60); |
| const sessionSeconds = state.sessionDuration % 60; |
| document.getElementById('session-time').textContent = |
| `${sessionMinutes.toString().padStart(2, '0')}:${sessionSeconds.toString().padStart(2, '0')}`; |
| } |
| |
| |
| function initVoiceControl() { |
| if (!('webkitSpeechRecognition' in window)) { |
| showTooltip('ГОЛОСОВОЕ УПРАВЛЕНИЕ НЕ ПОДДЕРЖИВАЕТСЯ ВАШИМ БРАУЗЕРОМ'); |
| return; |
| } |
| |
| state.voiceActive = !state.voiceActive; |
| |
| if (state.voiceActive) { |
| const recognition = new webkitSpeechRecognition(); |
| recognition.lang = 'ru-RU'; |
| recognition.interimResults = false; |
| |
| recognition.onstart = () => { |
| showTooltip('ГОЛОСОВОЕ УПРАВЛЕНИЕ АКТИВИРОВАНО. ГОВОРИТЕ КОМАНДЫ.'); |
| document.getElementById('voice-control').classList.add('cyber-voice-active'); |
| }; |
| |
| recognition.onresult = (e) => { |
| const transcript = e.results[0][0].transcript.toLowerCase(); |
| processVoiceCommand(transcript); |
| }; |
| |
| recognition.onerror = (e) => { |
| showTooltip('ОШИБКА РАСПОЗНАВАНИЯ ГОЛОСА: ' + e.error); |
| document.getElementById('voice-control').classList.remove('cyber-voice-active'); |
| state.voiceActive = false; |
| }; |
| |
| recognition.onend = () => { |
| if (state.voiceActive) { |
| recognition.start(); |
| } else { |
| document.getElementById('voice-control').classList.remove('cyber-voice-active'); |
| } |
| }; |
| |
| recognition.start(); |
| } else { |
| showTooltip('ГОЛОСОВОЕ УПРАВЛЕНИЕ ОТКЛЮЧЕНО'); |
| document.getElementById('voice-control').classList.remove('cyber-voice-active'); |
| } |
| } |
| |
| |
| function processVoiceCommand(command) { |
| showTooltip(`РАСПОЗНАНА КОМАНДА: "${command}"`); |
| |
| |
| if (command.includes('старт') || command.includes('начать')) { |
| startSession(); |
| return; |
| } |
| |
| if (command.includes('стоп') || command.includes('остановить')) { |
| stopSession(); |
| return; |
| } |
| |
| if (command.includes('пауза') || command.includes('приостановить')) { |
| togglePause(); |
| return; |
| } |
| |
| |
| if (command.includes('кристалл') || command.includes('кристаллический')) { |
| setMode('crystal'); |
| return; |
| } |
| |
| if (command.includes('гранул') || command.includes('фрактал')) { |
| setMode('granular'); |
| return; |
| } |
| |
| if (command.includes('3d') || command.includes('поток')) { |
| setMode('flow3d'); |
| return; |
| } |
| |
| if (command.includes('ритуал') || command.includes('мегалит')) { |
| setMode('ritual'); |
| return; |
| } |
| |
| |
| if (command.includes('громкость')) { |
| const volumeMatch = command.match(/громкость (\d+)/); |
| if (volumeMatch) { |
| const volume = parseInt(volumeMatch[1]) / 100; |
| if (volume >= 0 && volume <= 1) { |
| document.getElementById('volume').value = volume; |
| updateVolume({ target: document.getElementById('volume') }); |
| showTooltip(`ГРОМКОСТЬ УСТАНОВЛЕНА НА ${Math.round(volume * 100)}%`); |
| } |
| } |
| return; |
| } |
| |
| if (command.includes('тембр')) { |
| const timbreMatch = command.match(/тембр (\d+)/); |
| if (timbreMatch) { |
| const timbre = parseInt(timbreMatch[1]) / 100; |
| if (timbre >= 0 && timbre <= 1) { |
| document.getElementById('timbre').value = timbre; |
| updateTimbre({ target: document.getElementById('timbre') }); |
| showTooltip(`ТЕМБР УСТАНОВЛЕН НА ${Math.round(timbre * 100)}%`); |
| } |
| } |
| return; |
| } |
| |
| showTooltip('КОМАНДА НЕ РАСПОЗНАНА. ПОПРОБУЙТЕ СНОВА.'); |
| } |
| |
| |
| function showHelp() { |
| const helpText = ` |
| <div class="space-y-4"> |
| <h3 class="text-neon-cyan text-lg font-bold">ЦИФРОВОЙ РЕЗОНАТОР СОЗНАНИЯ 3.0</h3> |
| <p class="text-gray-300">Используйте этот инструмент для синхронизации вашего сознания с цифровой реальностью.</p> |
| |
| <div class="space-y-2"> |
| <h4 class="text-neon-pink font-bold">КОМАНДЫ:</h4> |
| <ul class="list-disc list-inside text-sm text-gray-400"> |
| <li>"Старт" - начать сеанс</li> |
| <li>"Стоп" - завершить сеанс</li> |
| <li>"Пауза" - приостановить сеанс</li> |
| <li>"Кристаллический" - активировать кристаллический режим</li> |
| <li>"Гранулярный" - активировать гранулярный режим</li> |
| <li>"3D поток" - активировать 3D режим</li> |
| <li>"Ритуал" - активировать ритуальный режим</li> |
| <li>"Громкость X" - установить громкость (0-100)</li> |
| <li>"Тембр X" - установить тембр (0-100)</li> |
| </ul> |
| </div> |
| |
| <div class="pt-4 border-t border-gray-800"> |
| <p class="text-xs text-gray-500">Версия 3.0 | Цифровая Революция</p> |
| </div> |
| </div> |
| `; |
| |
| const modal = document.createElement('div'); |
| modal.className = 'cyber-audio-modal'; |
| modal.innerHTML = ` |
| <div class="cyber-audio-modal-content p-6 cyber-card max-w-md"> |
| ${helpText} |
| <button id="close-help" class="cyber-btn w-full mt-4 py-2 rounded-lg"> |
| ЗАКРЫТЬ |
| </button> |
| </div> |
| `; |
| |
| document.body.appendChild(modal); |
| |
| document.getElementById('close-help').addEventListener('click', () => { |
| modal.remove(); |
| }); |
| } |
| |
| |
| function showTooltip(message) { |
| const tooltip = document.getElementById('tooltip'); |
| tooltip.textContent = message; |
| tooltip.classList.remove('hidden'); |
| |
| setTimeout(() => { |
| tooltip.classList.add('hidden'); |
| }, 3000); |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=seqinho/rezonator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |