lonestar108's picture
Particles have x, y, phase, and value
8b4661d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PhasePulse Particles Simulator</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/p5@1.7.0/lib/p5.min.js"></script>
</head>
<body class="bg-gray-900 text-white">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<header class="text-center mb-12">
<h1 class="text-4xl md:text-6xl font-bold bg-gradient-to-r from-purple-500 to-blue-500 bg-clip-text text-transparent mb-4">
PhasePulse Particles 🔬
</h1>
<p class="text-lg text-gray-300 max-w-2xl mx-auto">
A dynamic particle simulation where mathematical relationships create beautiful emergent behavior
</p>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Simulation Controls -->
<div class="lg:col-span-1 bg-gray-800 rounded-xl p-6">
<h2 class="text-2xl font-bold mb-6 flex items-center">
<i data-feather="sliders" class="mr-3"></i>
Simulation Controls
</h2>
<div class="space-y-6">
<!-- Particle Controls -->
<div>
<label class="block text-sm font-medium mb-2">Number of Particles</label>
<input type="range" min="10" max="200" value="50"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
id="particleCount">
<div class="flex justify-between text-xs text-gray-400">
<span>10</span>
<span id="currentCount">50</span>
<span>200</span>
</div>
</div>
<!-- Distance Controls -->
<div>
<label class="block text-sm font-medium mb-2">Interaction Distance</label>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="text-xs text-gray-400">Min Distance</label>
<input type="range" min="10" max="100" value="30"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
id="minDistance">
</div>
<div>
<label class="text-xs text-gray-400">Max Distance</label>
<input type="range" min="50" max="300" value="150"
class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
id="maxDistance">
</div>
</div>
</div>
<!-- Behavior Controls -->
<div class="space-y-4">
<button id="resetSim" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg transition-colors flex items-center justify-center">
<i data-feather="refresh-cw" class="mr-2 w-4 h-4"></i>
Reset Simulation
</button>
<button id="pauseSim" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition-colors flex items-center justify-center">
<i data-feather="pause" class="mr-2 w-4 h-4"></i>
Pause/Resume
</button>
</div>
<!-- Stats Display -->
<div class="bg-gray-700 rounded-lg p-4">
<h3 class="font-semibold mb-2">Simulation Stats</h3>
<div class="text-sm space-y-1">
<div class="flex justify-between">
<span>Active Particles:</span>
<span id="activeParticles">50</span>
</div>
<div class="flex justify-between">
<span>Average Phase:</span>
<span id="avgPhase">3.14</span>
</div>
<div class="flex justify-between">
<span>Prime Bonds:</span>
<span id="primeBonds">0</span>
</div>
</div>
</div>
</div>
</div>
<!-- Simulation Canvas -->
<div class="lg:col-span-2">
<div class="bg-gray-800 rounded-xl p-4">
<div id="simulationCanvas" class="w-full h-96 bg-gray-900 rounded-lg"></div>
</div>
<!-- Legend -->
<div class="mt-6 bg-gray-800 rounded-xl p-6">
<h3 class="text-xl font-bold mb-4 flex items-center">
<i data-feather="info" class="mr-2"></i>
How It Works
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-300">
<div>
<h4 class="font-semibold text-purple-400 mb-2">Phase Dynamics</h4>
<p>Particles have phases (0-2π) controlling attraction/repulsion. Phase 0 = maximum attraction, 2π = maximum repulsion.</p>
</div>
<div>
<h4 class="font-semibold text-blue-400 mb-2">Prime Connections</h4>
<p>Particles form special bonds with others that share prime factors in their values, creating unique clustering patterns.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Particle system implementation
class ParticleSystem {
constructor() {
this.particles = [];
this.isRunning = true;
this.canvas = null;
this.ctx = null;
this.minDistance = 30;
this.maxDistance = 150;
this.setupCanvas();
this.initializeParticles(50);
this.animate();
}
setupCanvas() {
const container = document.getElementById('simulationCanvas');
this.canvas = document.createElement('canvas');
this.canvas.width = container.clientWidth;
this.canvas.height = container.clientHeight;
container.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
}
initializeParticles(count) {
this.particles = [];
for (let i = 0; i < count; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
phase: Math.PI + (Math.random() * 0.1 - 0.05), // PI ± 5%
value: this.getRandomValue(),
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
connections: []
});
}
}
getRandomValue() {
// Returns a number between 2-100
return Math.floor(Math.random() * 99) + 2;
}
getPrimeFactors(n) {
const factors = [];
let divisor = 2;
while (n >= 2) {
if (n % divisor === 0) {
factors.push(divisor);
n = n / divisor;
} else {
divisor++;
}
}
return factors;
}
sharePrimeFactors(a, b) {
const factorsA = this.getPrimeFactors(a);
const factorsB = this.getPrimeFactors(b);
return factorsA.some(factor => factorsB.includes(factor));
}
update() {
if (!this.isRunning) return;
// Update particle positions
this.particles.forEach(particle => {
particle.x += particle.vx;
particle.y += particle.vy;
// Bounce off walls
if (particle.x < 0 || particle.x > this.canvas.width) particle.vx *= -1;
if (particle.y < 0 || particle.y > this.canvas.height) particle.vy *= -1;
// Keep particles in bounds
particle.x = Math.max(0, Math.min(this.canvas.width, particle.x));
particle.y = Math.max(0, Math.min(this.canvas.height, particle.y));
});
// Update phases based on interactions
this.updatePhases();
}
updatePhases() {
this.particles.forEach((particle, i) => {
let totalPhase = 0;
let count = 0;
let totalDistance = 0;
this.particles.forEach((other, j) => {
if (i === j) return;
const dx = particle.x - other.x;
const dy = particle.y - other.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance >= this.minDistance && distance <= this.maxDistance) {
totalPhase += other.phase;
totalDistance += distance;
count++;
}
});
if (count > 0) {
const avgPhase = totalPhase / count;
const avgDistance = totalDistance / count;
const phaseInfluence = (count * avgPhase) / avgDistance;
// Smooth phase adjustment
particle.phase += (phaseInfluence - particle.phase) * 0.01;
particle.phase = Math.max(0, Math.min(2 * Math.PI, particle.phase));
}
});
}
draw() {
this.ctx.fillStyle = 'rgb(17, 24, 39)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Draw connections first
this.particles.forEach(particle => {
particle.connections = [];
this.particles.forEach(other => {
if (particle === other) return;
const dx = particle.x - other.x;
const dy = particle.y - other.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Draw prime factor connections
if (this.sharePrimeFactors(particle.value, other.value) && distance < 100) {
particle.connections.push(other);
this.ctx.strokeStyle = 'rgba(139, 92, 246, 0.3)';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(particle.x, particle.y);
this.ctx.lineTo(other.x, other.y);
this.ctx.stroke();
}
});
});
// Draw particles
this.particles.forEach(particle => {
// Color based on phase (red = attracted, blue = repulsed)
const hue = (particle.phase / (2 * Math.PI)) * 240;
this.ctx.fillStyle = `hsl(${hue}, 70%, 60%)`;
// Size based on value
const size = 5 + (particle.value / 100) * 10;
this.ctx.beginPath();
this.ctx.arc(particle.x, particle.y, size, 0, 2 * Math.PI);
this.ctx.fill();
// Draw phase indicator
this.ctx.strokeStyle = 'white';
this.ctx.lineWidth = 1;
this.ctx.beginPath();
this.ctx.arc(particle.x, particle.y, size + 3, 0, particle.phase, false);
this.ctx.stroke();
});
// Update stats
this.updateStats();
}
updateStats() {
const avgPhase = this.particles.reduce((sum, p) => sum + p.phase, 0) / this.particles.length;
let primeBonds = 0;
this.particles.forEach(p => {
primeBonds += p.connections.length;
});
document.getElementById('activeParticles').textContent = this.particles.length;
document.getElementById('avgPhase').textContent = avgPhase.toFixed(2);
document.getElementById('primeBonds').textContent = primeBonds / 2; // Each bond counted twice
}
animate() {
this.update();
this.draw();
requestAnimationFrame(() => this.animate());
}
}
// Initialize simulation when page loads
let simulation;
document.addEventListener('DOMContentLoaded', function() {
simulation = new ParticleSystem();
feather.replace();
// Event listeners for controls
document.getElementById('particleCount').addEventListener('input', function(e) {
const count = parseInt(e.target.value);
document.getElementById('currentCount').textContent = count;
simulation.initializeParticles(count);
});
document.getElementById('minDistance').addEventListener('input', function(e) {
simulation.minDistance = parseInt(e.target.value);
});
document.getElementById('maxDistance').addEventListener('input', function(e) {
simulation.maxDistance = parseInt(e.target.value);
});
document.getElementById('resetSim').addEventListener('click', function() {
const count = parseInt(document.getElementById('particleCount').value);
simulation.initializeParticles(count);
});
document.getElementById('pauseSim').addEventListener('click', function() {
simulation.isRunning = !simulation.isRunning;
const button = document.getElementById('pauseSim');
const icon = button.querySelector('i');
if (simulation.isRunning) {
icon.setAttribute('data-feather', 'pause');
} else {
icon.setAttribute('data-feather', 'play');
}
feather.replace();
});
// Handle window resize
window.addEventListener('resize', function() {
const container = document.getElementById('simulationCanvas');
simulation.canvas.width = container.clientWidth;
simulation.canvas.height = container.clientHeight;
});
});
</script>
</body>
</html>