File size: 8,793 Bytes
bf84fea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Clear Outward Explosion - Low Lag</title>
<style>
body, html { margin:0; padding:0; width:100%; height:100%; background:#000; overflow:hidden; font-family:system-ui,sans-serif; }
#canvas-container { position:absolute; inset:0; z-index:1; }
#flash {
position:absolute; inset:0; background:linear-gradient(#ff450033, #8b000022); opacity:0;
z-index:5; pointer-events:none; transition:opacity 0.18s ease-out;
}
#flash.active { opacity:0.65; }
#flash.fade-out { opacity:0; transition:opacity 2s ease-out; }
#title-container {
position:absolute; inset:0; display:flex; align-items:center; justify-content:center; z-index:10; pointer-events:none;
}
#title {
color:#ffcc00; font-size:12vw; font-weight:900; text-transform:uppercase; letter-spacing:0.25em;
text-shadow:0 0 25px #ff6600aa, 0 0 60px #ff3300aa;
opacity:0; transform:scale(2.8); filter:blur(30px);
transition:all 0.8s cubic-bezier(0.15,1.4,0.3,1.1);
}
#title.revealed { opacity:0.92; transform:scale(1); filter:blur(0); }
#replay {
position:absolute; bottom:90px; left:50%; transform:translateX(-50%);
padding:18px 55px; background:transparent; color:#ffeb3b; border:1px solid #ff980055;
border-radius:50px; font-size:18px; letter-spacing:4px; text-transform:uppercase; cursor:pointer;
z-index:20; opacity:0; pointer-events:none; transition:all 0.9s;
}
#replay.visible { opacity:1; pointer-events:auto; }
#replay:hover { color:#fff; border-color:#ff9800; background:rgba(255,152,0,0.2); }
</style>
</head>
<body>
<div id="canvas-container"></div>
<div id="flash"></div>
<div id="title-container"><h1 id="title">BOOM</h1></div>
<button id="replay">RETRY</button>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/"
}
}
</script>
<script type="module">
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';
const container = document.getElementById('canvas-container');
const flashEl = document.getElementById('flash');
const titleEl = document.getElementById('title');
const replayBtn = document.getElementById('replay');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0014);
const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.5, 4000);
camera.position.set(0, 0, 14); // bit farther for better view of propagation
const renderer = new THREE.WebGLRenderer({antialias:false, powerPreference:"high-performance"});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.3));
container.appendChild(renderer.domElement);
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloom = new UnrealBloomPass(new THREE.Vector2(innerWidth, innerHeight), 0.7, 0.2, 0.85);
bloom.threshold = 0.7;
composer.addPass(bloom);
// βββ Particles with staggered birth βββ
const COUNT = 2000;
const geo = new THREE.BufferGeometry();
const pos = new Float32Array(COUNT*3);
const vel = new Float32Array(COUNT*3);
const col = new Float32Array(COUNT*3);
const siz = new Float32Array(COUNT);
const birth = new Float32Array(COUNT);
const palette = [
new THREE.Color(0xff6600),
new THREE.Color(0xff3300),
new THREE.Color(0xff9900),
new THREE.Color(0xffcc00),
new THREE.Color(0xff4422)
];
for(let i = 0; i < COUNT; i++){
// start clustered near center
pos[i*3 ] = (Math.random() - 0.5) * 0.6;
pos[i*3+1] = (Math.random() - 0.5) * 0.6;
pos[i*3+2] = (Math.random() - 0.5) * 0.6;
const dist = Math.hypot(pos[i*3], pos[i*3+1], pos[i*3+2]) || 0.01;
// outward direction + speed scaling with distance (faster outer particles)
let dx = pos[i*3 ] / dist;
let dy = pos[i*3+1] / dist;
let dz = pos[i*3+2] / dist;
const speed = 18 + dist * 45 + Math.random() * 15;
vel[i*3 ] = dx * speed;
vel[i*3+1] = dy * speed;
vel[i*3+2] = dz * speed;
const c = palette[Math.floor(Math.random() * palette.length)];
const bright = 0.65 + Math.random() * 0.35;
col[i*3] = c.r * bright;
col[i*3+1] = c.g * bright;
col[i*3+2] = c.b * bright;
siz[i] = 9 + Math.random() * 14;
// KEY CHANGE: stagger birth strongly β central particles first, outer later
birth[i] = - (dist * 1.2 + Math.random() * 0.5);
}
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
geo.setAttribute('velocity', new THREE.BufferAttribute(vel, 3));
geo.setAttribute('color', new THREE.BufferAttribute(col, 3));
geo.setAttribute('size', new THREE.BufferAttribute(siz, 1));
geo.setAttribute('birthTime', new THREE.BufferAttribute(birth, 1));
const mat = new THREE.ShaderMaterial({
uniforms: { uTime: {value:0} },
vertexShader: `
attribute vec3 velocity;
attribute vec3 color;
attribute float size;
attribute float birthTime;
varying vec3 vColor;
varying float vAlpha;
uniform float uTime;
void main(){
vColor = color;
float age = uTime - birthTime;
if(age < 0.0){ gl_Position=vec4(10000.0); vAlpha=0.0; return; }
float t = clamp(age * 1.3, 0.0, 1.0);
float eased = 1.0 - pow(1.0 - t, 2.5); // quick start, then decelerate
vec3 p = position + velocity * eased * 0.9;
float scale = 1.0 + (1.0 - eased) * 10.0; // bigger at birth
vec4 mv = modelViewMatrix * vec4(p, 1.0);
gl_PointSize = size * scale * (280.0 / -mv.z);
gl_Position = projectionMatrix * mv;
vAlpha = 1.0 - eased * eased; // smooth fade
}
`,
fragmentShader: `
varying vec3 vColor;
varying float vAlpha;
void main(){
vec2 c = gl_PointCoord - 0.5;
float d = length(c);
if(d > 0.5) discard;
gl_FragColor = vec4(vColor, (1.0 - d*d*1.8) * vAlpha * 0.95);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
scene.add(new THREE.Points(geo, mat));
// βββ Animation & Sequence βββ
const clock = new THREE.Clock();
let time = 0;
function resetExplosion() {
time = 0;
titleEl.classList.remove('revealed');
flashEl.classList.remove('active','fade-out');
replayBtn.classList.remove('visible');
const bt = geo.attributes.birthTime.array;
for(let i = 0; i < COUNT; i++) {
const dist = Math.hypot(pos[i*3], pos[i*3+1], pos[i*3+2]) || 0.01;
bt[i] = - (dist * 1.2 + Math.random() * 0.5);
}
geo.attributes.birthTime.needsUpdate = true;
}
function explode() {
resetExplosion();
setTimeout(() => {
flashEl.classList.add('active');
bloom.strength = 1.3;
setTimeout(() => flashEl.classList.add('fade-out'), 500);
setTimeout(() => {
bloom.strength = 0.55;
titleEl.classList.add('revealed');
setTimeout(() => replayBtn.classList.add('visible'), 200);
}, 70);
}, 30);
}
function animate() {
requestAnimationFrame(animate);
time += clock.getDelta();
mat.uniforms.uTime.value = time;
if(time > 3.5) bloom.strength = 0.55 + Math.sin(time * 1.1) * 0.1;
composer.render();
}
animate();
explode();
replayBtn.addEventListener('click', explode);
window.addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
composer.setSize(innerWidth, innerHeight);
});
</script>
</body>
</html> |