<!DOCTYPE html>
<html lang="en" class="h-full dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D WebGL Audio Spectrum Visualizer & Synth Studio</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body { margin: 0; background: #020617; overflow: hidden; font-family: 'Inter', sans-serif; }
#vizCanvas { width: 100%; height: 100%; touch-action: none; }
</style>
</head>
<body class="h-screen text-white flex flex-col select-none">
<!-- Header -->
<header class="h-14 bg-slate-900/90 backdrop-blur-md border-b border-slate-800 px-4 sm:px-6 flex items-center justify-between z-20">
<div class="flex items-center gap-3">
<span class="w-9 h-9 rounded-xl bg-gradient-to-tr from-purple-500 to-cyan-500 flex items-center justify-center text-lg shadow-lg">🎵</span>
<div>
<h1 class="font-bold text-sm sm:text-base text-white">3D WebGL Audio Spectrum & Synth Studio</h1>
<p class="text-[10px] text-cyan-400 font-mono">64-Band 3D Circular Frequency Ring + Web Audio Synth</p>
</div>
</div>
<div class="flex items-center gap-2">
<button id="beatToggleBtn" onclick="toggleSynthBeat()" class="px-4 py-1.5 rounded-xl bg-gradient-to-r from-cyan-500 to-blue-600 text-slate-950 font-bold text-xs shadow-lg transition">
🔊 Play Live Synth Beat
</button>
</div>
</header>
<div class="flex-1 relative overflow-hidden">
<canvas id="vizCanvas"></canvas>
<!-- Bottom Controls Bar -->
<div class="absolute bottom-5 left-1/2 -translate-x-1/2 bg-slate-900/85 backdrop-blur-xl border border-slate-800 rounded-2xl px-5 py-3 flex flex-wrap items-center gap-4 text-xs">
<button onclick="setPalette(0x06b6d4, 0xa855f7)" class="px-3 py-1.5 rounded-lg bg-cyan-500/20 text-cyan-300 font-bold">Cyber Neon</button>
<button onclick="setPalette(0x10b981, 0x3b82f6)" class="px-3 py-1.5 rounded-lg bg-emerald-500/20 text-emerald-300 font-bold">Aurora Pulse</button>
<button onclick="setPalette(0xf43f5e, 0xf59e0b)" class="px-3 py-1.5 rounded-lg bg-rose-500/20 text-rose-300 font-bold">Solar Flare</button>
</div>
</div>
<script>
const canvas = document.getElementById('vizCanvas');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / (window.innerHeight - 56), 0.1, 1000);
camera.position.set(0, 4.2, 7.5);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight - 56);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const ringGroup = new THREE.Group();
scene.add(ringGroup);
const bars = [];
const barCount = 64;
const radius = 2.8;
const barMat = new THREE.MeshStandardMaterial({ color: 0x06b6d4, emissive: 0x083344, metalness: 0.7, roughness: 0.2 });
for (let i = 0; i < barCount; i++) {
const angle = (i / barCount) * Math.PI * 2;
const geo = new THREE.BoxGeometry(0.14, 1, 0.14);
const mesh = new THREE.Mesh(geo, barMat.clone());
mesh.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
mesh.rotation.y = -angle;
ringGroup.add(mesh);
bars.push(mesh);
}
// Center Pulsing Core Sphere
const coreMat = new THREE.MeshStandardMaterial({ color: 0xa855f7, wireframe: true });
const coreSphere = new THREE.Mesh(new THREE.IcosahedronGeometry(1.1, 2), coreMat);
scene.add(coreSphere);
scene.add(new THREE.AmbientLight(0xffffff, 0.8));
const pLight = new THREE.PointLight(0x22d3ee, 2, 15);
pLight.position.set(0, 3, 2);
scene.add(pLight);
// Optional Web Audio Synth Beat Loop
let beatActive = false, audioCtx = null, beatTimer = null, step = 0;
const notes = [110, 146.83, 164.81, 220, 293.66, 329.63, 440, 220];
function toggleSynthBeat() {
beatActive = !beatActive;
const btn = document.getElementById('beatToggleBtn');
if (beatActive) {
btn.innerText = '⏸ Pause Synth Beat';
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
beatTimer = setInterval(() => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = step % 2 === 0 ? 'sawtooth' : 'triangle';
osc.frequency.setValueAtTime(notes[step % notes.length], audioCtx.currentTime);
gain.gain.setValueAtTime(0.06, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.18);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.18);
step++;
}, 200);
} else {
btn.innerText = '🔊 Play Live Synth Beat';
clearInterval(beatTimer);
}
}
function setPalette(barHex, coreHex) {
bars.forEach(b => b.material.color.setHex(barHex));
coreMat.color.setHex(coreHex);
}
let t = 0;
function animate() {
requestAnimationFrame(animate);
t += 0.045;
ringGroup.rotation.y += 0.006;
coreSphere.rotation.x += 0.01;
coreSphere.rotation.y += 0.012;
const pulse = 1 + Math.sin(t * 3) * (beatActive ? 0.28 : 0.12);
coreSphere.scale.set(pulse, pulse, pulse);
bars.forEach((b, idx) => {
const wave = Math.abs(Math.sin(t * 2.2 + idx * 0.35) * Math.cos(t * 1.3 - idx * 0.2));
const scaleY = 0.3 + wave * (beatActive ? 3.2 : 1.8);
b.scale.y = scaleY;
b.position.y = scaleY / 2 - 0.5;
});
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / (window.innerHeight - 56);
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight - 56);
});
</script>
</body>
</html>
/* Built-in 3D WebGL & Glassmorphic Shaders included in index.html */
// Complete Three.js 3D scene, camera, lighting, and pointer controls included