rezonator / index.html
seqinho's picture
Add 3 files
3a79daf verified
<!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">
<!-- 3D элементы добавляются динамически -->
</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, // 30 минут в секундах
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', () => {
// Инициализация canvas
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('ОШИБКА: НЕ УДАЛОСЬ АКТИВИРОВАТЬ АУДИО СИСТЕМУ');
}
}
// Инициализация canvas для осциллографа
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);
// Очищаем canvas
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;
// 3D эффект с перспективой
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);
// Обновляем UI
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;
// Обновляем UI
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];
// Обновляем активный элемент в UI
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);
}
}
}
// Инициализация 3D потока
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;
// Нормализуем координаты (0..1)
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 };
// Нормализуем координаты (0..1)
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>