CSS
<style>
body .experiment-container { margin: 0; overflow: hidden; background-color: #0c0c0f; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: monospace; }
#audioWaveCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
#youtube-container { position: relative; z-index: 2; width: 80vw; max-width: 800px; aspect-ratio: 16 / 9; background-color: #000; border-radius: 8px; overflow: hidden; transition: box-shadow 0.3s ease; }
#player { width: 100%; height: 100%; }
#controls-dialog { position: absolute; top: 20px; right: 20px; background-color: rgba(30, 30, 30, 0.9); border-radius: 8px; padding: 15px; box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); z-index: 10; max-height: calc(100vh - 40px); overflow-y: auto; color: #eee; width: 250px; border: 1px solid rgba(255, 255, 255, 0.1); }
#controls-dialog h3 { margin-top: 0; text-align: center; font-weight: 300; letter-spacing: 1px; }
.control-group { margin-bottom: 12px; }
.control-group label { display: block; margin-bottom: 5px; font-size: 0.9em; color: #ccc; }
.control-group input[type="range"] { width: 100%; }
.control-group span { font-size: 0.8em; float: right; color: #aaa; }
#manual-toggle-button {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 10;
padding: 10px 15px;
font-family: monospace;
background-color: #00ffff;
color: #0c0c0f;
border: none;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 0 10px #00ffff;
}
</style>
JS
<script>
const canvas = document.getElementById('audioWaveCanvas');
const ctx = canvas.getContext('2d');
const youtubeContainer = document.getElementById('youtube-container');
let width, height;
const dataPoints = 128;
let audioData = new Float32Array(dataPoints).fill(0);
let targetAudioData = new Float32Array(dataPoints).fill(0);
let player;
let isApiReady = false;
let playerState = -1;
let isManualToggleActive = false;
let mouseX = -1000, mouseY = -1000;
const INTERACTION_RADIUS = 200;
let lastIdleUpdateTime = 0;
let lastActiveUpdateTime = 0;
const particles = [];
const MAX_PARTICLES = 3000;
let particleEmissionTimer = 0;
const PARTICLE_EMISSION_RATE = 2;
class Particle {
constructor(x, y, controls) {
this.x = x; this.y = y;
const speedMultiplier = 0.2 + Math.random() * 0.8;
this.vx = (Math.random() - 0.5) * 3 * speedMultiplier;
this.vy = (Math.random() - 0.5) * 3 * speedMultiplier;
this.life = 0;
this.maxLife = controls.particleLifespan * (0.5 + Math.random());
this.size = 0.5 + Math.random() * 2.5;
this.hue = controls.colorHue + (Math.random() - 0.5) * 20;
}
update() { this.x += this.vx; this.y += this.vy; this.life++; return this.life < this.maxLife; }
draw() {
const alpha = 1 - (this.life / this.maxLife);
ctx.fillStyle = `hsla(${this.hue}, 100%, 80%, ${alpha})`;
ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill();
}
}
const controls = {
idleAmplitude: 1, activeAmplitude: 7.5, pulseSpeed: 50, activeSpeed: 150, waveFlow: 0.005, cursorPush: 0.5,
lineCount: 100, fraying: 6.9, colorHue: 180, particleSpeed: 2,
particleLifespan: 190, particleBurst: 3,
};
function setupControls() {
Object.keys(controls).forEach(key => {
const slider = document.getElementById(`${key}-slider`);
const valueSpan = document.getElementById(`${key}-val`);
if (slider) {
slider.oninput = (e) => {
controls[key] = parseFloat(e.target.value);
if (valueSpan) valueSpan.textContent = controls[key];
if (key === 'colorHue') updateYoutubeGlow();
};
}
});
updateYoutubeGlow();
}
function updateYoutubeGlow() {
youtubeContainer.style.boxShadow = `0 0 35px hsla(${controls.colorHue}, 90%, 50%, 0.6)`;
}
function resizeCanvas() { width = canvas.width = window.innerWidth; height = canvas.height = window.innerHeight; }
window.addEventListener('resize', resizeCanvas); resizeCanvas();
window.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; });
window.addEventListener('mouseleave', () => { mouseX = -1000; mouseY = -1000; });
function onYouTubeIframeAPIReady() {
isApiReady = true;
player = new YT.Player('player', {
height: '100%', width: '100%', videoId: 'ughJpX003Vw',
playerVars: { 'playsinline': 1, 'origin': window.location.origin, 'controls': 1, 'rel': 0, 'enablejsapi': 1 },
events: { 'onStateChange': onPlayerStateChange }
});
}
function onPlayerStateChange(event) { playerState = event.data; }
function drawDebugInfo(engineName) {
ctx.font = "16px monospace"; ctx.shadowColor = "#000"; ctx.shadowBlur = 5;
let statusText, statusColor;
if (isApiReady && playerState === YT.PlayerState.PLAYING) { statusText = "YT STATE: PLAYING"; statusColor = "#00ff00"; }
else { statusText = `YT STATE: INACTIVE (${playerState})`; statusColor = "#cccccc"; }
ctx.fillStyle = statusColor; ctx.fillText(statusText, 15, 30);
ctx.fillStyle = "#00ffff"; ctx.fillText(`ENGINE: ${engineName}`, 15, 50);
ctx.fillStyle = "#ffff00"; ctx.fillText(`Particles: ${particles.length}`, 15, 70);
ctx.shadowBlur = 0;
}
function runIdleEngine(timestamp) {
if (timestamp - lastIdleUpdateTime > controls.pulseSpeed) {
const amp = 10 * controls.idleAmplitude;
for (let i = 0; i < dataPoints; i++) { targetAudioData[i] = (Math.random() - 0.5) * amp; }
lastIdleUpdateTime = timestamp;
}
for (let i = 0; i < dataPoints; i++) { audioData[i] += (targetAudioData[i] - audioData[i]) * 0.1; }
}
function runActiveEngine(timestamp) {
if (timestamp - lastActiveUpdateTime > controls.activeSpeed) {
const amp = 15 * controls.activeAmplitude;
for (let i = 0; i < dataPoints; i++) {
const slowSine = Math.sin(i * 0.1 + timestamp * controls.waveFlow);
const mediumSine = Math.sin(i * 0.4 + timestamp * controls.waveFlow * 2);
const fastSine = Math.sin(i * 0.9 + timestamp * controls.waveFlow * 4);
targetAudioData[i] = (slowSine * 0.5 + mediumSine * 0.3 + fastSine * 0.2) * amp;
}
lastActiveUpdateTime = timestamp;
}
for (let i = 0; i < dataPoints; i++) {
audioData[i] += (targetAudioData[i] - audioData[i]) * 0.1;
}
}
function animate(timestamp) {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
let currentEngine = "IDLE ENGINE";
const isActive = (isApiReady && playerState === YT.PlayerState.PLAYING) || isManualToggleActive;
if (isActive) {
currentEngine = "ACTIVE ENGINE";
runActiveEngine(timestamp);
} else {
runIdleEngine(timestamp);
}
const waveCenterY = height / 2;
particleEmissionTimer--;
for (let lineIndex = 0; lineIndex < controls.lineCount; lineIndex++) {
ctx.beginPath();
const centerDist = Math.abs(lineIndex - controls.lineCount / 2);
const maxDist = controls.lineCount / 2;
const brightness = 1 - (centerDist / maxDist) * 0.6;
const opacity = 1.0 - (centerDist / maxDist) * 0.8;
ctx.strokeStyle = `hsla(${controls.colorHue}, 90%, ${brightness * 100}%, ${opacity})`;
ctx.lineWidth = 2.5 - (centerDist / maxDist) * 2.0;
ctx.lineJoin = 'miter';
for (let i = 0; i < dataPoints; i++) {
const x = (i / (dataPoints - 1)) * width;
let y = waveCenterY + audioData[i] * (1 - (centerDist / maxDist) * 0.5);
const frayAmount = centerDist * controls.fraying * (audioData[i] / 50) * (Math.random() - 0.5);
y += frayAmount;
const dx = x - mouseX; const dy = y - mouseY;
const distToMouse = Math.sqrt(dx * dx + dy * dy);
if (distToMouse < INTERACTION_RADIUS) {
const force = 1 - distToMouse / INTERACTION_RADIUS;
const displacement = force * 150 * controls.cursorPush;
y += (dy / (distToMouse || 1)) * displacement;
}
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
}
ctx.stroke();
}
if (isActive && particleEmissionTimer <= 0 && particles.length < MAX_PARTICLES) {
const ampThreshold = (15 * controls.activeAmplitude) * 0.5;
for (let i = 0; i < dataPoints; i++) {
if (Math.abs(audioData[i]) > ampThreshold) {
for(let p = 0; p < controls.particleBurst; p++) {
const x = (i / (dataPoints - 1)) * width;
const y = waveCenterY + audioData[i];
particles.push(new Particle(x, y, controls));
}
}
}
particleEmissionTimer = PARTICLE_EMISSION_RATE;
}
ctx.shadowBlur = 0;
for (let i = particles.length - 1; i >= 0; i--) {
if (particles[i].update()) particles[i].draw(); else particles.splice(i, 1);
}
drawDebugInfo(currentEngine);
}
const toggleButton = document.getElementById('manual-toggle-button');
toggleButton.addEventListener('click', () => {
isManualToggleActive = !isManualToggleActive;
toggleButton.textContent = isManualToggleActive ? 'Deactivate Wave' : 'Activate Wave';
});
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
setupControls();
animate(0);
</script>