Created
February 13, 2026 01:52
-
-
Save kazzohikaru/db5c8eb1430a6584fca022fabae0bdf7 to your computer and use it in GitHub Desktop.
Fireworks 2026 + Boom Sound
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="canvas-container"></div> | |
| <div id="overlay"> | |
| <div>CLICK TO START</div> | |
| <span>Feature: <span class="highlight">Fireworks with Explosion Deep Boom Sound</span></span> | |
| </div> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/[email protected]/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/" | |
| } | |
| } | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as THREE from 'three'; | |
| import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; | |
| import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; | |
| import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; | |
| import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | |
| // --- DEEP BASS AUDIO ENGINE --- | |
| const AudioSys = { | |
| ctx: null, | |
| enabled: false, | |
| volume: 0.6, | |
| limiter: null, | |
| init: function() { | |
| if (!this.ctx) { | |
| this.ctx = new (window.AudioContext || window.webkitAudioContext)(); | |
| this.limiter = this.ctx.createDynamicsCompressor(); | |
| this.limiter.threshold.value = -10; | |
| this.limiter.knee.value = 40; | |
| this.limiter.ratio.value = 12; | |
| this.limiter.connect(this.ctx.destination); | |
| this.enabled = true; | |
| } | |
| if (this.ctx.state === 'suspended') { | |
| this.ctx.resume(); | |
| } | |
| }, | |
| playDeepExplosion: function() { | |
| if (!this.enabled || !this.ctx) return; | |
| const t = this.ctx.currentTime; | |
| // --- 1. SUB-BASS KICK --- | |
| const osc = this.ctx.createOscillator(); | |
| const oscGain = this.ctx.createGain(); | |
| osc.type = 'sine'; | |
| osc.frequency.setValueAtTime(50, t); | |
| osc.frequency.exponentialRampToValueAtTime(20, t + 2.5); | |
| oscGain.gain.setValueAtTime(this.volume * 1.5, t); | |
| oscGain.gain.exponentialRampToValueAtTime(0.01, t + 5.0); | |
| osc.connect(oscGain); | |
| oscGain.connect(this.limiter); | |
| osc.start(t); | |
| osc.stop(t + 5.0); | |
| const bufferSize = this.ctx.sampleRate * 5.0; | |
| const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate); | |
| const data = buffer.getChannelData(0); | |
| for (let i = 0; i < bufferSize; i++) { | |
| data[i] = Math.random() * 2 - 1; | |
| } | |
| const noise = this.ctx.createBufferSource(); | |
| noise.buffer = buffer; | |
| const noiseFilter = this.ctx.createBiquadFilter(); | |
| noiseFilter.type = 'lowpass'; | |
| noiseFilter.frequency.setValueAtTime(150, t); | |
| noiseFilter.frequency.exponentialRampToValueAtTime(30, t + 4.0); | |
| const noiseGain = this.ctx.createGain(); | |
| noiseGain.gain.setValueAtTime(this.volume * 1.0, t); | |
| noiseGain.gain.exponentialRampToValueAtTime(0.01, t + 4.5); | |
| noise.connect(noiseFilter); | |
| noiseFilter.connect(noiseGain); | |
| noiseGain.connect(this.limiter); | |
| noise.start(t); | |
| const crack = this.ctx.createOscillator(); | |
| crack.type = 'triangle'; | |
| const crackGain = this.ctx.createGain(); | |
| crack.frequency.setValueAtTime(200, t); | |
| crack.frequency.exponentialRampToValueAtTime(50, t + 0.1); | |
| crackGain.gain.setValueAtTime(this.volume * 0.3, t); | |
| crackGain.gain.exponentialRampToValueAtTime(0.01, t + 0.1); | |
| crack.connect(crackGain); | |
| crackGain.connect(this.limiter); | |
| crack.start(t); | |
| crack.stop(t + 0.1); | |
| } | |
| }; | |
| // --- SPRITE GENERATOR --- | |
| function getSprite() { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = 32; | |
| canvas.height = 32; | |
| const context = canvas.getContext('2d'); | |
| const gradient = context.createRadialGradient(16, 16, 0, 16, 16, 16); | |
| gradient.addColorStop(0, 'rgba(255,255,255,1)'); | |
| gradient.addColorStop(0.3, 'rgba(255,255,255,0.9)'); | |
| gradient.addColorStop(0.5, 'rgba(255,255,255,0.5)'); | |
| gradient.addColorStop(1, 'rgba(0,0,0,0)'); | |
| context.fillStyle = gradient; | |
| context.fillRect(0, 0, 32, 32); | |
| return new THREE.CanvasTexture(canvas); | |
| } | |
| const particleSprite = getSprite(); | |
| // --- CONFIG --- | |
| const CONFIG = { | |
| particleCount: 23000, | |
| particleSize: 0.8, | |
| fadeSpeed: 0.00482, | |
| explosionForce: 3.3975, | |
| hoverDuration: 1.5, | |
| gravity: 0.00265, | |
| friction: 0.95494, | |
| spreadFall: 0, | |
| rocketSpeed: 1.0, | |
| rocketSize: 2.0, | |
| bloomStrength: 1.495, | |
| bloomRadius: 0.5, | |
| trailOpacity: 0.39707, | |
| autoLaunch: true, | |
| launchInterval: 3856.5, | |
| soundEnabled: true, | |
| volume: 0.5 | |
| }; | |
| // --- SCENE --- | |
| const container = document.getElementById('canvas-container'); | |
| const scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0x000000, 0.002); | |
| const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 4000); | |
| camera.position.set(0, 0, 150); | |
| const renderer = new THREE.WebGLRenderer({ antialias: false, preserveDrawingBuffer: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); | |
| renderer.autoClearColor = false; | |
| container.appendChild(renderer.domElement); | |
| // --- POST-PROCESSING --- | |
| const renderScene = new RenderPass(scene, camera); | |
| const bloomPass = new UnrealBloomPass( | |
| new THREE.Vector2(window.innerWidth, window.innerHeight), | |
| CONFIG.bloomStrength, CONFIG.bloomRadius, 0.0 | |
| ); | |
| const composer = new EffectComposer(renderer); | |
| composer.addPass(renderScene); | |
| composer.addPass(bloomPass); | |
| // --- BACKGROUND --- | |
| const starsGeo = new THREE.BufferGeometry(); | |
| const starsCnt = 3000; | |
| const sPos = new Float32Array(starsCnt * 3); | |
| for(let i=0; i<starsCnt*3; i++) sPos[i] = (Math.random() - 0.5) * 1200; | |
| starsGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3)); | |
| const starsMat = new THREE.PointsMaterial({ | |
| size: 1.5, color: 0x888888, map: particleSprite, | |
| transparent: true, depthWrite: false, blending: THREE.AdditiveBlending | |
| }); | |
| scene.add(new THREE.Points(starsGeo, starsMat)); | |
| const fadeMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x000000, transparent: true, opacity: CONFIG.trailOpacity | |
| }); | |
| const fullScreenQuad = new THREE.Mesh(new THREE.PlaneGeometry(4000, 4000), fadeMaterial); | |
| fullScreenQuad.position.z = camera.position.z - 50; | |
| fullScreenQuad.lookAt(camera.position); | |
| scene.add(fullScreenQuad); | |
| // --- GUI --- | |
| const gui = new GUI({ title: 'Settings' }); | |
| const fParticles = gui.addFolder('Particles'); | |
| fParticles.add(CONFIG, 'particleCount', 1000, 30000, 100); | |
| fParticles.add(CONFIG, 'particleSize', 0.1, 2.0); | |
| fParticles.add(CONFIG, 'fadeSpeed', 0.001, 0.02); | |
| const fPhysics = gui.addFolder('Physics'); | |
| fPhysics.add(CONFIG, 'explosionForce', 0.5, 5.0); | |
| fPhysics.add(CONFIG, 'hoverDuration', 0.0, 5.0); | |
| fPhysics.add(CONFIG, 'gravity', 0.0, 0.02); | |
| const fAudio = gui.addFolder('Deep Audio'); | |
| fAudio.add(CONFIG, 'soundEnabled'); | |
| fAudio.add(CONFIG, 'volume', 0.0, 1.0).onChange(v => AudioSys.volume = v); | |
| // --- FIREWORK CLASS --- | |
| class Firework { | |
| constructor(startX) { | |
| this.isDead = false; | |
| this.phase = 'rocket'; | |
| this.timer = 0; | |
| // --- COLOR LOGIC (Mono, Bi, Tri) --- | |
| const rand = Math.random(); | |
| const baseHue = Math.random(); | |
| this.colors = []; | |
| if (rand < 0.33) { | |
| // MONO (1 Color) | |
| this.colors.push(new THREE.Color().setHSL(baseHue, 1.0, 0.6)); | |
| } else if (rand < 0.66) { | |
| // DUAL (2 Colors - Complementary) | |
| this.colors.push(new THREE.Color().setHSL(baseHue, 1.0, 0.6)); | |
| this.colors.push(new THREE.Color().setHSL((baseHue + 0.5) % 1.0, 1.0, 0.5)); | |
| } else { | |
| // TRI (3 Colors - Triad) | |
| this.colors.push(new THREE.Color().setHSL(baseHue, 1.0, 0.6)); | |
| this.colors.push(new THREE.Color().setHSL((baseHue + 0.33) % 1.0, 1.0, 0.6)); | |
| this.colors.push(new THREE.Color().setHSL((baseHue + 0.66) % 1.0, 1.0, 0.6)); | |
| } | |
| this.pos = new THREE.Vector3(startX, -80, (Math.random()-0.5)*50); | |
| this.vel = new THREE.Vector3( | |
| (Math.random() - 0.5) * 0.5, | |
| CONFIG.rocketSpeed * (0.9 + Math.random() * 0.3), | |
| (Math.random() - 0.5) * 0.5 | |
| ); | |
| this.targetY = -10 + Math.random() * 30; | |
| const geo = new THREE.BufferGeometry(); | |
| geo.setAttribute('position', new THREE.Float32BufferAttribute(this.pos.toArray(), 3)); | |
| this.rocketMesh = new THREE.Points(geo, new THREE.PointsMaterial({ | |
| size: CONFIG.rocketSize, | |
| color: this.colors[0], // Use first color for rocket | |
| map: particleSprite, | |
| transparent: true, | |
| depthWrite: false, | |
| blending: THREE.AdditiveBlending | |
| })); | |
| scene.add(this.rocketMesh); | |
| } | |
| update(dt) { | |
| if (this.sparkSystem) this.sparkSystem.material.size = CONFIG.particleSize; | |
| if (this.rocketMesh) this.rocketMesh.material.size = CONFIG.rocketSize; | |
| if (this.phase === 'rocket') { | |
| this.pos.add(this.vel); | |
| this.vel.y *= 0.99; | |
| this.rocketMesh.geometry.attributes.position.setXYZ(0, this.pos.x, this.pos.y, this.pos.z); | |
| this.rocketMesh.geometry.attributes.position.needsUpdate = true; | |
| if (this.vel.y < 0.2 || this.pos.y >= this.targetY) this.explode(); | |
| } | |
| else { | |
| this.timer += dt; | |
| const positions = this.sparkSystem.geometry.attributes.position.array; | |
| const colors = this.sparkSystem.geometry.attributes.color.array; | |
| let aliveCount = 0; | |
| const isHovering = this.timer < CONFIG.hoverDuration; | |
| const gravityFactor = THREE.MathUtils.smoothstep(this.timer, CONFIG.hoverDuration, CONFIG.hoverDuration + 0.5); | |
| for(let i=0; i < this.currentParticleCount; i++) { | |
| if (this.lifetimes[i] > 0) { | |
| aliveCount++; | |
| const i3 = i*3; | |
| positions[i3] += this.velocities[i3]; | |
| positions[i3+1] += this.velocities[i3+1]; | |
| positions[i3+2] += this.velocities[i3+2]; | |
| if (isHovering) { | |
| this.velocities[i3] *= CONFIG.friction; | |
| this.velocities[i3+1] *= CONFIG.friction; | |
| this.velocities[i3+2] *= CONFIG.friction; | |
| } else { | |
| this.velocities[i3+1] -= CONFIG.gravity * gravityFactor; | |
| this.velocities[i3] *= 0.98; | |
| this.velocities[i3+1] *= 0.98; | |
| this.velocities[i3+2] *= 0.98; | |
| this.lifetimes[i] -= CONFIG.fadeSpeed; | |
| } | |
| const alpha = Math.max(0, this.lifetimes[i]); | |
| colors[i3] = this.baseColors[i3] * alpha * 1.5; | |
| colors[i3+1] = this.baseColors[i3+1] * alpha * 1.5; | |
| colors[i3+2] = this.baseColors[i3+2] * alpha * 1.5; | |
| } | |
| } | |
| this.sparkSystem.geometry.attributes.position.needsUpdate = true; | |
| this.sparkSystem.geometry.attributes.color.needsUpdate = true; | |
| if (aliveCount === 0) this.cleanup(); | |
| } | |
| } | |
| explode() { | |
| if (CONFIG.soundEnabled) AudioSys.playDeepExplosion(); | |
| scene.remove(this.rocketMesh); | |
| this.phase = 'explode'; | |
| this.timer = 0; | |
| this.currentParticleCount = CONFIG.particleCount; | |
| const geo = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(this.currentParticleCount * 3); | |
| const colors = new Float32Array(this.currentParticleCount * 3); | |
| this.baseColors = new Float32Array(this.currentParticleCount * 3); | |
| this.velocities = new Float32Array(this.currentParticleCount * 3); | |
| this.lifetimes = new Float32Array(this.currentParticleCount); | |
| const baseSpeed = CONFIG.explosionForce * (0.8 + Math.random() * 0.4); | |
| for(let i=0; i<this.currentParticleCount; i++) { | |
| const i3 = i*3; | |
| positions[i3] = this.pos.x; positions[i3+1] = this.pos.y; positions[i3+2] = this.pos.z; | |
| const theta = Math.random() * Math.PI * 2; | |
| const phi = Math.acos(2 * Math.random() - 1); | |
| const speed = baseSpeed * (0.8 + Math.random() * 0.4); | |
| this.velocities[i3] = speed * Math.sin(phi) * Math.cos(theta); | |
| this.velocities[i3+1] = speed * Math.sin(phi) * Math.sin(theta); | |
| this.velocities[i3+2] = speed * Math.cos(phi); | |
| // --- COLOR SELECTION & BRIGHTNESS VARIATION --- | |
| // Pick one of the generated colors for this particle | |
| const targetColor = this.colors[Math.floor(Math.random() * this.colors.length)]; | |
| // Vary brightness (0.5 to 1.3) to create depth | |
| const brightness = 0.5 + Math.random() * 0.8; | |
| this.baseColors[i3] = targetColor.r * brightness; | |
| this.baseColors[i3+1] = targetColor.g * brightness; | |
| this.baseColors[i3+2] = targetColor.b * brightness; | |
| colors[i3] = this.baseColors[i3]; | |
| colors[i3+1] = this.baseColors[i3+1]; | |
| colors[i3+2] = this.baseColors[i3+2]; | |
| this.lifetimes[i] = 1.0; | |
| } | |
| geo.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| geo.setAttribute('color', new THREE.BufferAttribute(colors, 3)); | |
| this.sparkSystem = new THREE.Points(geo, new THREE.PointsMaterial({ | |
| size: CONFIG.particleSize, | |
| map: particleSprite, | |
| transparent: true, | |
| depthWrite: false, | |
| vertexColors: true, | |
| blending: THREE.AdditiveBlending | |
| })); | |
| scene.add(this.sparkSystem); | |
| } | |
| cleanup() { | |
| this.isDead = true; | |
| if(this.sparkSystem) { | |
| scene.remove(this.sparkSystem); | |
| this.sparkSystem.geometry.dispose(); | |
| this.sparkSystem.material.dispose(); | |
| } | |
| if(this.rocketMesh) { | |
| scene.remove(this.rocketMesh); | |
| this.rocketMesh.geometry.dispose(); | |
| this.rocketMesh.material.dispose(); | |
| } | |
| } | |
| } | |
| // --- LOOP --- | |
| const fireworks = []; | |
| let lastLaunchTime = 0; | |
| let nextLaunchDelay = 0; | |
| function launchFirework() { | |
| const x = (Math.random() - 0.5) * 150; | |
| fireworks.push(new Firework(x)); | |
| } | |
| function updateQueue(time) { | |
| if (!CONFIG.autoLaunch) return; | |
| if (time - lastLaunchTime > nextLaunchDelay) { | |
| lastLaunchTime = time; | |
| nextLaunchDelay = CONFIG.launchInterval + (Math.random() * 1000); | |
| launchFirework(); | |
| } | |
| } | |
| const clock = new THREE.Clock(); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const dt = clock.getDelta(); | |
| updateQueue(performance.now()); | |
| for (let i = fireworks.length - 1; i >= 0; i--) { | |
| const fw = fireworks[i]; | |
| fw.update(dt); | |
| if (fw.isDead) fireworks.splice(i, 1); | |
| } | |
| composer.render(); | |
| } | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| bloomPass.resolution.set(window.innerWidth, window.innerHeight); | |
| composer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Overlay & Audio Init | |
| const overlay = document.getElementById('overlay'); | |
| overlay.addEventListener('click', () => { | |
| AudioSys.init(); | |
| overlay.style.display = 'none'; | |
| launchFirework(); | |
| }); | |
| animate(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body { margin: 0; overflow: hidden; background-color: #000; } | |
| #canvas-container { width: 100vw; height: 100vh; } | |
| .lil-gui.root { | |
| position: absolute !important; | |
| top: auto !important; | |
| bottom: 20px !important; | |
| right: 20px !important; | |
| } | |
| #overlay { | |
| position: absolute; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| background: rgba(0,0,0,0.8); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| font-family: monospace; | |
| font-size: 24px; | |
| cursor: pointer; | |
| z-index: 1000; | |
| flex-direction: column; | |
| text-align: center; | |
| } | |
| #overlay span { | |
| font-size: 14px; | |
| color: #888; | |
| margin-top: 15px; | |
| } | |
| .highlight { color: #ffaa00; font-weight: bold; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment