<div class="blackhole-container" style="width: 1000px; height: 1000px; position: relative; background-color: #000;">
<canvas style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></canvas>
<script>
// Self-executing function to encapsulate the code and keep it from interfering with other scripts.
(function() {
// This makes the element self-contained. It finds the canvas inside its own container.
const container = document.currentScript.parentElement;
const canvas = container.querySelector('canvas');
if (!container || !canvas) return;
const ctx = canvas.getContext('2d');
let particles = [];
const particleCount = 50000;
const triangleParticleCount = 35000;
const angleX = Math.PI / 2;
const cosX = Math.cos(angleX);
const sinX = Math.sin(angleX);
const singularityRadius = 100;
const photonSphereRadius = 100;
// --- ORIGINAL PARTICLE CLASS (UNCHANGED) ---
class Particle {
constructor(diskType) {
this.diskType = diskType; // 'main' or 'perpendicular'
this.reset();
}
reset() {
let radius;
if (this.diskType === 'main') {
radius = photonSphereRadius + Math.random() * (350 / 25);
} else {
radius = photonSphereRadius + Math.random() * 350;
}
const angle = Math.random() * Math.PI * 2;
const tubeRadius = 15 * (1 - Math.pow(radius / 450, 2));
const tubeAngle = Math.random() * Math.PI * 2;
if (this.diskType === 'main') {
this.x = (radius + Math.cos(tubeAngle) * tubeRadius) * Math.cos(angle);
this.z = (radius + Math.cos(tubeAngle) * tubeRadius) * Math.sin(angle);
this.y = Math.sin(tubeAngle) * tubeRadius;
} else {
this.x = (radius + Math.cos(tubeAngle) * tubeRadius) * Math.cos(angle);
this.y = (radius + Math.cos(tubeAngle) * tubeRadius) * Math.sin(angle);
this.z = Math.sin(tubeAngle) * tubeRadius;
}
this.speed = (0.001 + (1 / radius) * 0.2) * 10;
this.angle = angle;
this.radius = radius;
this.life = Math.random() * 200 + 100;
this.age = 0;
}
update() {
this.age++;
this.angle += this.speed;
if (this.diskType === 'main') {
this.x = this.radius * Math.cos(this.angle);
this.z = this.radius * Math.sin(this.angle);
} else {
this.x = this.radius * Math.cos(this.angle);
this.y = this.radius * Math.sin(this.angle);
}
if (this.age >= this.life || this.radius < photonSphereRadius) {
this.reset();
}
}
project(rotX, rotY, rotZ) {
const fov = 1000;
let dist = fov + rotZ;
this.isBehind = rotZ > 0;
if (dist > 0) {
this.projectedX = (rotX * fov) / dist + centerX;
this.projectedY = (rotY * fov) / dist + centerY;
this.size = Math.max(0, (2 - rotZ / 500) * 0.6);
} else {
this.size = -1;
}
}
draw() {
if (this.size <= 0) return;
const opacity = (1 - (this.age / this.life)) * 0.9;
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.fillRect(this.projectedX, this.projectedY, this.size, this.size);
}
}
// --- FINAL WORKING PARTICLE CLASS for Z-AXIS TRIANGLES ---
class TriangleParticle {
constructor(side) {
this.side = side; // 'left' or 'right'
this.reset();
}
reset() {
const radius = 100;
const extension = 300;
const joinZMax = 90;
const vertexX = radius + extension;
const joinX = Math.sqrt(Math.max(0, radius * radius - joinZMax * joinZMax));
const parabola_a = joinZMax * joinZMax / (joinX - vertexX);
let x, z;
let valid = false;
while(!valid) {
z = (Math.random() - 0.5) * (2 * joinZMax);
const inner_sqrt_arg = radius * radius - z * z;
if (inner_sqrt_arg < 0) continue;
const x_inner_boundary = Math.sqrt(inner_sqrt_arg);
const x_outer_boundary = (z * z / parabola_a) + vertexX;
if(x_outer_boundary > x_inner_boundary){
x = x_outer_boundary - Math.random() * (x_outer_boundary - x_inner_boundary) * 0.1;
valid = true;
}
}
this.x = x;
this.z = z;
if (this.side === 'left') {
this.x = -this.x;
}
this.y = (Math.random() - 0.5) * 15;
this.age = 0;
this.life = 100;
// Enforce inward speed
this.speedX = (this.side === 'left' ? 1 : -1) * (1 + Math.random() * 2.5);
this.speedY = (Math.random() - 0.5) * 2.5;
this.speedZ = (Math.random() - 0.5) * 2.5;
}
update() {
this.age++;
// Apply a constant inward pull on all axes
const dx = 0 - this.x;
const dy = 0 - this.y;
const dz = 0 - this.z;
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (distance > 0) {
this.speedX = (dx / distance) * 2.5;
this.speedY = (dy / distance) * 2.5;
this.speedZ = (dz / distance) * 2.5;
}
this.x += this.speedX;
this.y += this.speedY;
this.z += this.speedZ;
// Reset if the particle reaches the center
if (distance < singularityRadius) {
this.reset();
}
}
project(rotX, rotY, rotZ) {
const fov = 1000;
let dist = fov + rotZ;
this.isBehind = rotZ > 0;
if (dist > 0) {
this.projectedX = (rotX * fov) / dist + centerX;
this.projectedY = (rotY * fov) / dist + centerY;
this.size = Math.max(0, (2 - rotZ / 500) * 0.6);
} else {
this.size = -1;
}
}
draw() {
if (this.size <= 0) return;
// Fade out as they move inward
const opacity = (1 - (this.age / this.life)) * 0.9;
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.fillRect(this.projectedX, this.projectedY, this.size, this.size);
}
}
let centerX, centerY;
function init() {
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
centerX = canvas.width / 2;
centerY = canvas.height / 2;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle(i < particleCount / 2 ? 'main' : 'perpendicular'));
}
for (let i = 0; i < triangleParticleCount; i++) {
particles.push(new TriangleParticle(i < triangleParticleCount / 2 ? 'left' : 'right'));
}
animate();
}
function drawSingularity() {
ctx.beginPath();
ctx.arc(centerX, centerY, singularityRadius, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
}
function animate() {
requestAnimationFrame(animate);
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const foregroundParticles = [];
const backgroundParticles = [];
particles.forEach(p => {
p.update();
const y_rotated = p.y * cosX - p.z * sinX;
const z_rotated = p.y * sinX + p.z * cosX;
const x_final = p.x;
const y_final = y_rotated;
const z_final = z_rotated;
p.project(x_final, y_final, z_final);
if (p.isBehind) backgroundParticles.push(p);
else foregroundParticles.push(p);
});
ctx.shadowBlur = 8;
ctx.shadowColor = "rgba(255, 255, 255, 0.2)";
backgroundParticles.forEach(p => p.draw());
ctx.shadowBlur = 0;
drawSingularity();
ctx.shadowBlur = 8;
ctx.shadowColor = "rgba(255, 255, 255, 0.2)";
foregroundParticles.forEach(p => p.draw());
ctx.shadowBlur = 0;
}
init();
})();
</script>
</div>