Initial commit: party-mix-app with prefetch cache, audio preload optimizations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 12:40:22 +03:00
commit 0097fb5183
83 changed files with 11788 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
'use client'
import { useEffect, useRef } from 'react'
import { audioState } from '@/lib/audioState'
export default function AudioBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
let rafId: number
let smoothBass = 0
let smoothMid = 0
let dataBuf: Uint8Array<ArrayBuffer> | null = null
const resize = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
}
resize()
window.addEventListener('resize', resize)
const draw = () => {
rafId = requestAnimationFrame(draw)
const W = canvas.width
const H = canvas.height
let rawBass = 0
let rawMid = 0
if (audioState.analyser && audioState.isPlaying) {
const binCount = audioState.analyser.frequencyBinCount
if (!dataBuf || dataBuf.length !== binCount) dataBuf = new Uint8Array(new ArrayBuffer(binCount))
audioState.analyser.getByteFrequencyData(dataBuf)
const bassEnd = Math.max(1, Math.ceil(binCount * 0.1))
for (let i = 0; i < bassEnd; i++) rawBass = Math.max(rawBass, dataBuf[i] / 255)
const midStart = bassEnd
const midEnd = Math.ceil(binCount * 0.4)
let midSum = 0
for (let i = midStart; i < midEnd; i++) midSum += dataBuf[i] / 255
rawMid = midSum / Math.max(1, midEnd - midStart)
}
smoothBass += (rawBass - smoothBass) * 0.1
smoothMid += (rawMid - smoothMid) * 0.07
const t = Date.now() / 4500
const breathe = Math.sin(t) * 0.03 + Math.cos(t * 0.7) * 0.015
ctx.clearRect(0, 0, W, H)
ctx.fillStyle = '#0a0a0f'
ctx.fillRect(0, 0, W, H)
const diag = Math.hypot(W, H)
const base = diag * 0.55
// Lime orb — bass driven, top-left
const r1 = base * (0.75 + smoothBass * 0.55 + breathe)
const a1 = 0.055 + smoothBass * 0.07
const g1 = ctx.createRadialGradient(W * 0.18, H * 0.08, 0, W * 0.18, H * 0.08, r1)
g1.addColorStop(0, `rgba(200,255,0,${a1})`)
g1.addColorStop(0.45, `rgba(200,255,0,${a1 * 0.28})`)
g1.addColorStop(1, 'rgba(200,255,0,0)')
ctx.fillStyle = g1
ctx.fillRect(0, 0, W, H)
// Pink orb — mid driven, bottom-right
const r2 = base * (0.65 + smoothMid * 0.45 - breathe * 0.6)
const a2 = 0.045 + smoothMid * 0.055
const g2 = ctx.createRadialGradient(W * 0.82, H * 0.92, 0, W * 0.82, H * 0.92, r2)
g2.addColorStop(0, `rgba(255,60,172,${a2})`)
g2.addColorStop(0.45, `rgba(255,60,172,${a2 * 0.28})`)
g2.addColorStop(1, 'rgba(255,60,172,0)')
ctx.fillStyle = g2
ctx.fillRect(0, 0, W, H)
// Purple orb — combined, center, fades in when playing
const combined = smoothBass * 0.55 + smoothMid * 0.45
if (combined > 0.005) {
const r3 = base * (0.38 + combined * 0.32)
const a3 = combined * 0.035
const g3 = ctx.createRadialGradient(W * 0.5, H * 0.52, 0, W * 0.5, H * 0.52, r3)
g3.addColorStop(0, `rgba(140,100,255,${a3})`)
g3.addColorStop(1, 'rgba(140,100,255,0)')
ctx.fillStyle = g3
ctx.fillRect(0, 0, W, H)
}
}
draw()
return () => {
cancelAnimationFrame(rafId)
window.removeEventListener('resize', resize)
}
}, [])
return (
<canvas
ref={canvasRef}
className="fixed inset-0 w-full h-full pointer-events-none"
style={{ zIndex: 0 }}
/>
)
}