diff --git a/index.html b/index.html index af52118..861cfe3 100644 --- a/index.html +++ b/index.html @@ -38,9 +38,10 @@ body { font-family:"Orbitron", sans-serif; background: var(--bg); color: var(--t .terminal-window .content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 10px; } .terminal-window .content::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.3); } -/* Video container for pipes terminal */ +/* Visualizer canvas container */ .video-container { width: 100%; height: 100%; position: relative; overflow: hidden; border-radius: 0 0 14px 14px; background: transparent; display: flex; align-items: center; justify-content: center; } -.video-container video { width: 100%; height: 100%; object-fit: cover; display: block; background: transparent; } +#vizCanvas { width: 100%; height: 100%; display: block; border-radius: 0 0 13px 13px; } +.viz-idle-msg { position: absolute; color: rgba(255,255,255,0.22); font-size: 11px; letter-spacing: 2px; pointer-events: none; text-transform: uppercase; } /* Loading screen */ #loadingScreen { @@ -251,8 +252,8 @@ body { font-family:"Orbitron", sans-serif; background: var(--bg); color: var(--t visualizer
-
Loading...
- + + play music to visualize
@@ -355,15 +356,12 @@ bgMusic.addEventListener('ended', () => { // Loading screen functionality const loadingScreen = document.getElementById('loadingScreen'); const bgVideo = document.getElementById('bgVideo'); -const pipesVideo = document.getElementById('pipesVideo'); -const pipesPlaceholder = document.getElementById('pipesPlaceholder'); loadingScreen.addEventListener('click', () => { - // Hide loading screen loadingScreen.classList.add('hidden'); - // Start videos - tryAutoLoad(); + // Load background video + tryLoadBgVideo(); // Auto-play music setTimeout(() => { @@ -379,77 +377,151 @@ loadingScreen.addEventListener('click', () => { }, 500); }); -// Try to auto-load if videos are in same directory (fallback) -async function tryAutoLoad() { - try { - const bgResponse = await fetch('backgroundVideo.mp4'); - if (bgResponse.ok) { - const blob = await bgResponse.blob(); - const url = URL.createObjectURL(blob); - bgVideo.src = url; - bgVideo.load(); - bgVideo.play().catch(e => console.error('BG video error:', e)); - } - } catch (e) { - console.log('Background video not found, trying .webm'); +async function tryLoadBgVideo() { + for (const name of ['backgroundVideo.mp4', 'backgroundVideo.webm']) { try { - const bgResponse = await fetch('backgroundVideo.webm'); - if (bgResponse.ok) { - const blob = await bgResponse.blob(); - const url = URL.createObjectURL(blob); + const res = await fetch(name); + if (res.ok) { + const url = URL.createObjectURL(await res.blob()); bgVideo.src = url; bgVideo.load(); - bgVideo.play().catch(e => console.error('BG video error:', e)); + bgVideo.play().catch(() => {}); + return; } - } catch (e2) { - console.error('No background video found'); + } catch (e) {} + } +} + +// ── Audio Visualizer ────────────────────────────────────────────── +const vizCanvas = document.getElementById('vizCanvas'); +const vizCtx = vizCanvas.getContext('2d'); +const vizIdle = document.getElementById('vizIdleMsg'); + +let audioCtx, analyser, source, vizAnimId; +let vizConnected = false; + +function setupAnalyser() { + if (vizConnected) return; + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + analyser = audioCtx.createAnalyser(); + analyser.fftSize = 256; + analyser.smoothingTimeConstant = 0.82; + source = audioCtx.createMediaElementSource(bgMusic); + source.connect(analyser); + analyser.connect(audioCtx.destination); + vizConnected = true; +} + +function resizeCanvas() { + vizCanvas.width = vizCanvas.offsetWidth * devicePixelRatio; + vizCanvas.height = vizCanvas.offsetHeight * devicePixelRatio; +} + +function drawViz() { + vizAnimId = requestAnimationFrame(drawViz); + resizeCanvas(); + + const W = vizCanvas.width, H = vizCanvas.height; + const bufLen = analyser.frequencyBinCount; + const data = new Uint8Array(bufLen); + analyser.getByteFrequencyData(data); + + // Clear with a slight fade trail for smoothness + vizCtx.clearRect(0, 0, W, H); + + const barCount = 80; + const gap = W * 0.004; + const barW = (W - gap * (barCount + 1)) / barCount; + const step = Math.floor(bufLen / barCount); + const maxH = H * 0.82; + const baseY = H * 0.92; + + for (let i = 0; i < barCount; i++) { + // Average a small bucket for smoother freq representation + let sum = 0; + for (let k = 0; k < step; k++) sum += data[i * step + k]; + const avg = sum / step; + const ratio = avg / 255; + + const bh = ratio * maxH; + const x = gap + i * (barW + gap); + const y = baseY - bh; + + // Bar opacity and glow scale with amplitude + const alpha = 0.25 + ratio * 0.75; + + // Subtle vertical gradient: bright top → dimmer base + const grad = vizCtx.createLinearGradient(0, y, 0, baseY); + grad.addColorStop(0, `rgba(255,255,255,${alpha})`); + grad.addColorStop(0.6, `rgba(220,220,220,${alpha * 0.6})`); + grad.addColorStop(1, `rgba(180,180,180,${alpha * 0.15})`); + + vizCtx.fillStyle = grad; + + // Rounded top on the bar + const radius = Math.min(barW / 2, 4); + vizCtx.beginPath(); + vizCtx.moveTo(x + radius, y); + vizCtx.lineTo(x + barW - radius, y); + vizCtx.quadraticCurveTo(x + barW, y, x + barW, y + radius); + vizCtx.lineTo(x + barW, baseY); + vizCtx.lineTo(x, baseY); + vizCtx.lineTo(x, y + radius); + vizCtx.quadraticCurveTo(x, y, x + radius, y); + vizCtx.fill(); + + // Subtle glow on loud bars + if (ratio > 0.5) { + vizCtx.save(); + vizCtx.shadowColor = `rgba(255,255,255,${(ratio - 0.5) * 0.6})`; + vizCtx.shadowBlur = 8 * ratio; + vizCtx.fill(); + vizCtx.restore(); + } + + // Floating dot cap above bar + if (bh > 4) { + vizCtx.fillStyle = `rgba(255,255,255,${alpha * 0.9})`; + vizCtx.fillRect(x, y - 3, barW, 2); } } - try { - const pipesResponse = await fetch('terminal.mp4'); - if (pipesResponse.ok) { - const blob = await pipesResponse.blob(); - const url = URL.createObjectURL(blob); - pipesVideo.src = url; - pipesVideo.style.display = 'block'; - pipesPlaceholder.style.display = 'none'; - pipesVideo.load(); - pipesVideo.play().catch(e => console.error('Pipes video error:', e)); - } - } catch (e) { - console.log('Pipes video not found, trying .webm or pipes.mp4'); - try { - const pipesResponse = await fetch('terminal.webm'); - if (pipesResponse.ok) { - const blob = await pipesResponse.blob(); - const url = URL.createObjectURL(blob); - pipesVideo.src = url; - pipesVideo.style.display = 'block'; - pipesPlaceholder.style.display = 'none'; - pipesVideo.load(); - pipesVideo.play().catch(e => console.error('Pipes video error:', e)); - } - } catch (e2) { - // Try pipes.mp4 as fallback - try { - const pipesResponse = await fetch('pipes.mp4'); - if (pipesResponse.ok) { - const blob = await pipesResponse.blob(); - const url = URL.createObjectURL(blob); - pipesVideo.src = url; - pipesVideo.style.display = 'block'; - pipesPlaceholder.style.display = 'none'; - pipesVideo.load(); - pipesVideo.play().catch(e => console.error('Pipes video error:', e)); - } - } catch (e3) { - console.error('No pipes video found'); - pipesPlaceholder.textContent = 'No video found'; - } - } - } + // Baseline rule + vizCtx.strokeStyle = 'rgba(255,255,255,0.08)'; + vizCtx.lineWidth = 1; + vizCtx.beginPath(); + vizCtx.moveTo(0, baseY); + vizCtx.lineTo(W, baseY); + vizCtx.stroke(); } + +function startViz() { + setupAnalyser(); + if (audioCtx.state === 'suspended') audioCtx.resume(); + vizIdle.style.opacity = '0'; + cancelAnimationFrame(vizAnimId); + drawViz(); +} + +function stopViz() { + cancelAnimationFrame(vizAnimId); + // Draw flat line when paused + resizeCanvas(); + const W = vizCanvas.width, H = vizCanvas.height; + vizCtx.clearRect(0, 0, W, H); + vizCtx.strokeStyle = 'rgba(255,255,255,0.1)'; + vizCtx.lineWidth = 1; + vizCtx.beginPath(); + vizCtx.moveTo(0, H * 0.92); + vizCtx.lineTo(W, H * 0.92); + vizCtx.stroke(); + vizIdle.style.opacity = '1'; +} + +// Hook into existing play/pause logic +bgMusic.addEventListener('play', () => startViz()); +bgMusic.addEventListener('pause', () => stopViz()); +bgMusic.addEventListener('ended', () => stopViz());