phaseflow-particles / index.html
lonestar108's picture
Manual changes saved
c397312 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PhaseFlow Particles</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
canvas {
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
}
.control-panel {
backdrop-filter: blur(10px);
background: rgba(15, 23, 42, 0.7);
}
</style>
</head>
<body class="bg-gray-900 text-white overflow-hidden">
<div class="relative h-screen w-full">
<canvas id="particleCanvas" class="absolute top-0 left-0 w-full h-full"></canvas>
<!-- Control Panel -->
<div class="control-panel absolute top-4 right-4 rounded-xl p-4 shadow-lg z-10 w-80">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold flex items-center">
<i data-feather="settings" class="mr-2"></i> Controls
</h2>
<button id="resetBtn" class="bg-indigo-600 hover:bg-indigo-700 px-3 py-1 rounded-lg text-sm flex items-center">
<i data-feather="refresh-ccw" class="mr-1"></i> Reset
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Particle Count: <span id="countValue">150</span></label>
<input type="range" id="particleCount" min="50" max="300" value="150" class="w-full accent-indigo-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Min Distance: <span id="minDistValue">30</span>px</label>
<input type="range" id="minDistance" min="10" max="100" value="30" class="w-full accent-indigo-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Max Distance: <span id="maxDistValue">120</span>px</label>
<input type="range" id="maxDistance" min="50" max="200" value="120" class="w-full accent-indigo-500">
</div>
<div>
<label class="block text-sm font-medium mb-1">Interaction Strength: <span id="strengthValue">0.5</span></label>
<input type="range" id="interactionStrength" min="0.1" max="2" step="0.1" value="0.5" class="w-full accent-indigo-500">
</div>
<div class="flex space-x-2 pt-2">
<button id="pauseBtn" class="flex-1 bg-amber-600 hover:bg-amber-700 py-2 rounded-lg text-sm flex items-center justify-center">
<i data-feather="pause" class="mr-1"></i> Pause
</button>
<button id="infoBtn" class="flex-1 bg-cyan-600 hover:bg-cyan-700 py-2 rounded-lg text-sm flex items-center justify-center">
<i data-feather="info" class="mr-1"></i> Info
</button>
</div>
</div>
</div>
<!-- Info Modal -->
<div id="infoModal" class="hidden fixed inset-0 bg-black bg-opacity-70 z-20 flex items-center justify-center p-4">
<div class="control-panel rounded-xl p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold">PhaseFlow Simulation</h2>
<button id="closeInfo" class="text-gray-400 hover:text-white">
<i data-feather="x"></i>
</button>
</div>
<div class="space-y-4 text-gray-200">
<p>Each particle has:</p>
<ul class="list-disc pl-5 space-y-2">
<li><span class="font-semibold">Position (x, y)</span>: Location on the canvas</li>
<li><span class="font-semibold">Phase (0-2π)</span>: Controls attraction/repulsion behavior
<ul class="list-circle pl-5 mt-1">
<li>0: Strongly attracted</li>
<li>π: Balanced (attracted & repulsed)</li>
<li>2π: Strongly repulsed</li>
</ul>
</li>
<li><span class="font-semibold">Value (2-100)</span>: Determines secondary magnetic attraction based on shared prime factors</li>
</ul>
<p class="pt-2">Particles influence each other through:</p>
<ul class="list-disc pl-5 space-y-2">
<li>Phase interactions within min/max distance range</li>
<li>Magnetic attraction to particles sharing prime factors</li>
<li>Phase adjustment formula: (particle count × average phase) / average distance</li>
</ul>
</div>
</div>
</div>
</div>
<script>
// Initialize feather icons
feather.replace();
// Canvas setup
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Simulation parameters
let params = {
particleCount: 150,
minDistance: 30,
maxDistance: 120,
interactionStrength: 0.5,
paused: false
};
// Particle class
class Particle {
constructor(x, y) {
this.x = x || Math.random() * canvas.width;
this.y = y || Math.random() * canvas.height;
this.phase = Math.PI + (Math.random() * 0.1 - 0.05); // PI ± 5%
this.value = Math.floor(Math.random() * 99) + 2; // 2-100
this.vx = (Math.random() - 0.5) * 0.5;
this.vy = (Math.random() - 0.5) * 0.5;
this.radius = 3 + (this.value / 30);
this.color = this.getPhaseColor();
this.primeFactors = this.getPrimeFactors(this.value);
}
getPrimeFactors(n) {
const factors = [];
let divisor = 2;
while (n >= 2) {
if (n % divisor === 0) {
factors.push(divisor);
n = n / divisor;
} else {
divisor++;
}
}
return [...new Set(factors)]; // Unique factors
}
getPhaseColor() {
// Map phase to color: 0=blue, π=green, 2π=red
const hue = (this.phase / (2 * Math.PI)) * 360;
return `hsl(${hue}, 80%, 60%)`;
}
update(particles) {
if (params.paused) return;
// Apply velocity
this.x += this.vx;
this.y += this.vy;
// Boundary conditions
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
// Interaction calculations
let fx = 0;
let fy = 0;
let nearbyParticles = 0;
let totalPhase = 0;
let totalDistance = 0;
for (let other of particles) {
if (other === this) continue;
const dx = other.x - this.x;
const dy = other.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > params.minDistance && distance < params.maxDistance) {
// Primary phase-based force
const force = Math.PI + Math.cos(this.phase) * params.interactionStrength / distance;
fx += force * dx;
fy += force * dy;
// Collect data for phase influence
nearbyParticles++;
totalPhase += other.phase;
totalDistance += distance;
// Secondary magnetic attraction based on shared prime factors
const sharedFactors = this.primeFactors.filter(factor =>
other.primeFactors.includes(factor));
if (sharedFactors.length > 0) {
const magneticForce = sharedFactors.length * 0.05 / distance;
this.vx += magneticForce * dx * 0.01;
this.vy += magneticForce * dy * 0.01;
}
}
}
// Apply phase influence from nearby particles
if (nearbyParticles > 0) {
const avgPhase = totalPhase / nearbyParticles;
const avgDistance = totalDistance / nearbyParticles;
const phaseInfluence = (nearbyParticles * avgPhase) / avgDistance * 0.1;
this.phase = (this.phase + phaseInfluence) % (2 * Math.PI);
if (this.phase < 0) this.phase += 2 * Math.PI;
}
// Apply forces
this.vx += fx * 0.01;
this.vy += fy * 0.01;
// Velocity damping
this.vx *= 0.96;
this.vy *= 0.96;
// Update color based on new phase
this.color = this.getPhaseColor();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
// Draw value indicator
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius * 0.4, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.fill();
}
}
// Create particles
let particles = [];
function initParticles() {
particles = [];
for (let i = 0; i < params.particleCount; i++) {
particles.push(new Particle());
}
}
// Animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw connections between close particles
ctx.lineWidth = 0.5;
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const p1 = particles[i];
const p2 = particles[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < params.maxDistance) {
const alpha = 1 - dist / params.maxDistance;
ctx.strokeStyle = `rgba(100, 150, 255, ${alpha * 0.2})`;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
}
}
// Update and draw particles
particles.forEach(particle => {
particle.update(particles);
particle.draw();
});
requestAnimationFrame(animate);
}
// Event listeners for controls
document.getElementById('particleCount').addEventListener('input', function() {
params.particleCount = parseInt(this.value);
document.getElementById('countValue').textContent = this.value;
initParticles();
});
document.getElementById('minDistance').addEventListener('input', function() {
params.minDistance = parseInt(this.value);
document.getElementById('minDistValue').textContent = this.value;
});
document.getElementById('maxDistance').addEventListener('input', function() {
params.maxDistance = parseInt(this.value);
document.getElementById('maxDistValue').textContent = this.value;
});
document.getElementById('interactionStrength').addEventListener('input', function() {
params.interactionStrength = parseFloat(this.value);
document.getElementById('strengthValue').textContent = this.value;
});
document.getElementById('pauseBtn').addEventListener('click', function() {
params.paused = !params.paused;
const icon = this.querySelector('i');
if (params.paused) {
icon.setAttribute('data-feather', 'play');
this.innerHTML = '<i data-feather="play" class="mr-1"></i> Resume';
} else {
icon.setAttribute('data-feather', 'pause');
this.innerHTML = '<i data-feather="pause" class="mr-1"></i> Pause';
}
feather.replace();
});
document.getElementById('resetBtn').addEventListener('click', function() {
initParticles();
});
document.getElementById('infoBtn').addEventListener('click', function() {
document.getElementById('infoModal').classList.remove('hidden');
});
document.getElementById('closeInfo').addEventListener('click', function() {
document.getElementById('infoModal').classList.add('hidden');
});
// Handle window resize
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// Initialize and start simulation
initParticles();
animate();
</script>
</body>
</html>