⭐ Built in the Agent OS — get it inside the AI Profit BoardroomJoin AIPB →

Fireworks

Click or tap anywhere to launch

``` Now, write the JS code: ```javascript // Configuration const CONFIG = { gravity: 180, drag: 0.96, trailLength: 12, maxParticles: 4000, maxFireworks: 8, colors: [ 0xff0040, 0xff4000, 0xff8000, 0xffff00, 0x80ff00, 0x00ff40, 0x00ffff, 0x0080ff, 0x4000ff, 0xff00ff, 0xff0080, 0xffffff ] }; // State let scene, camera, renderer, clock; let particleSystem, trailSystem; let particleGeo, trailGeo; let particlePositions, particleColors, particleSizes; let trailPositions, trailColors; let particles = []; let raycaster, mousePlane; let launchSynth, explodeSynth; let audioStarted = false; function init() { // Scene scene = new THREE.Scene(); scene.fog = new THREE.FogExp2(0x000000, 0.0008); // Camera camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000); camera.position.z = 800; // Renderer renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); // Clock clock = new THREE.Clock(); // Raycaster and plane for mouse interaction raycaster = new THREE.Raycaster(); mousePlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); // Starfield createStars(); // Particle texture const texture = createParticleTexture(); // Particle system particleGeo = new THREE.BufferGeometry(); particlePositions = new Float32Array(CONFIG.maxParticles * 3); particleColors = new Float32Array(CONFIG.maxParticles * 4); particleSizes = new Float32Array(CONFIG.maxParticles); particleGeo.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3)); particleGeo.setAttribute('color', new THREE.BufferAttribute(particleColors, 4)); particleGeo.setAttribute('size', new THREE.BufferAttribute(particleSizes, 1)); particleGeo.setDrawRange(0, 0); const particleMaterial = new THREE.PointsMaterial({ size: 1, map: texture, vertexColors: true, blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, opacity: 1, sizeAttenuation: true }); particleSystem = new THREE.Points(particleGeo, particleMaterial); scene.add(particleSystem); // Trail system const trailVertexCount = CONFIG.maxParticles * (CONFIG.trailLength - 1) * 2; trailGeo = new THREE.BufferGeometry(); trailPositions = new Float32Array(trailVertexCount * 3); trailColors = new Float32Array(trailVertexCount * 4); trailGeo.setAttribute('position', new THREE.BufferAttribute(trailPositions, 3)); trailGeo.setAttribute('color', new THREE.BufferAttribute(trailColors, 4)); trailGeo.setDrawRange(0, 0); const trailMaterial = new THREE.LineBasicMaterial({ vertexColors: true, blending: THREE.AdditiveBlending, depthWrite: false, transparent: true, opacity: 1 }); trailSystem = new THREE.LineSegments(trailGeo, trailMaterial); scene.add(trailSystem); // Events window.addEventListener('resize', onWindowResize); window.addEventListener('pointerdown', onPointerDown); window.addEventListener('touchstart', onTouchStart, { passive: false }); // Start loop animate(); } function createParticleTexture() { const canvas = document.createElement('canvas'); canvas.width = 64; canvas.height = 64; const ctx = canvas.getContext('2d'); const grad = ctx.createRadialGradient(32, 32, 0, 32, 32, 32); grad.addColorStop(0, 'rgba(255,255,255,1)'); grad.addColorStop(0.1, 'rgba(255,255,255,0.8)'); grad.addColorStop(0.4, 'rgba(255,255,255,0.2)'); grad.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = grad; ctx.fillRect(0, 0, 64, 64); const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; return texture; } function createStars() { const starGeo = new THREE.BufferGeometry(); const starCount = 1500; const positions = new Float32Array(starCount * 3); const colors = new Float32Array(starCount * 3); for (let i = 0; i < starCount; i++) { const r = 800 + Math.random() * 1000; const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); positions[i*3] = r * Math.sin(phi) * Math.cos(theta); positions[i*3+1] = r * Math.sin(phi) * Math.sin(theta); positions[i*3+2] = r * Math.cos(phi); const shade = 0.5 + Math.random() * 0.5; colors[i*3] = shade; colors[i*3+1] = shade; colors[i*3+2] = shade; } starGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); starGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3)); const starMat = new THREE.PointsMaterial({ size: 2, vertexColors: true, transparent: true, opacity: 0.8, sizeAttenuation: true }); const stars = new THREE.Points(starGeo, starMat); scene.add(stars); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function onPointerDown(event) { event.preventDefault(); startAudio(); const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); raycaster.setFromCamera(mouse, camera); const target = new THREE.Vector3(); raycaster.ray.intersectPlane(mousePlane, target); if (target) { launchFirework(target.x, target.y); } } function onTouchStart(event) { event.preventDefault(); if (event.touches.length > 0) { const touch = event.touches[0]; onPointerDown({ clientX: touch.clientX, clientY: touch.clientY, preventDefault: () => {} }); } } async function startAudio() { if (audioStarted) return; try { await Tone.start(); audioStarted = true; launchSynth = new Tone.PolySynth(Tone.Synth, { oscillator: { type: "sine" }, envelope: { attack: 0.01, decay: 0.3, sustain: 0, release: 0.3 } }).toDestination(); launchSynth.volume.value = -12; explodeSynth = new Tone.NoiseSynth({ noise: { type: "pink" }, envelope: { attack: 0.01, decay: 0.6, sustain: 0, release: 0.5 } }).toDestination(); explodeSynth.volume.value = -8; } catch (e) { console.warn("Audio init failed", e); } } function playLaunchSound() { if (!audioStarted || !launchSynth) return; const notes = ["C5", "D5", "E5", "G5", "A5"]; const note = notes[Math.floor(Math.random() * notes.length)]; launchSynth.triggerAttackRelease(note, "16n"); } function playExplodeSound() { if (!audioStarted || !explodeSynth) return; explodeSynth.triggerAttackRelease("8n"); } function launchFirework(targetX, targetY) { const startX = targetX + (Math.random() - 0.5) * 100; const startY = -window.innerHeight / 2 - 50; const startZ = 0; const target = new THREE.Vector3(targetX, targetY, 0); const start = new THREE.Vector3(startX, startY, startZ); const duration = 1.0 + Math.random() * 0.6; // Calculate initial velocity to reach target under gravity const vx = (targetX - startX) / duration; const vy = (targetY - startY) / duration + 0.5 * CONFIG.gravity * duration; const vz = 0; const color = CONFIG.colors[Math.floor(Math.random() * CONFIG.colors.length)]; const rocket = { id: nextParticleId++, pos: new THREE.Vector3(startX, startY, startZ), vel: new THREE.Vector3(vx, vy, vz), acc: new THREE.Vector3(0, -CONFIG.gravity, 0), life: duration, maxLife: duration, color: new THREE.Color(color), size: 6, trail: [], type: 'rocket' }; // Fill initial trail for (let i = 0; i < CONFIG.trailLength; i++) { rocket.trail.push(start.clone()); } addParticle(rocket); playLaunchSound(); // Schedule explosion setTimeout(() => { explode(targetX, targetY, 0, color); }, duration * 1000); } function explode(x, y, z, color) { playExplodeSound(); const baseColor = new THREE.Color(color); const count = 80 + Math.floor(Math.random() * 80); const pattern = Math.random(); for (let i = 0; i < count; i++) { let vel; if (pattern < 0.5) { // spherical explosion const theta = Math.random() * Math.PI * 2; const phi = Math.acos(2 * Math.random() - 1); const speed = 60 + Math.random() * 120; vel = new THREE.Vector3( speed * Math.sin(phi) * Math.cos(theta), speed * Math.sin(phi) * Math.sin(theta), speed * Math.cos(phi) ); } else if (pattern < 0.8) { // ring explosion const theta = Math.random() * Math.PI * 2; const