Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>QuantumPrimeSync: Particle Harmony Nexus</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <style> | |
| @keyframes pulse { | |
| 0% { opacity: 0.6; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.6; } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| .particle-trail { | |
| position: absolute; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| opacity: 0.7; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white overflow-hidden"> | |
| <!-- Header --> | |
| <div class="absolute top-0 left-0 w-full z-20"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="flex items-center mb-4 md:mb-0"> | |
| <h1 class="text-3xl font-bold text-purple-400">QuantumPrimeSync</h1> | |
| <span class="text-2xl ml-2">🌌</span> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-sm">Particles: <span id="particleCount" class="font-mono">0</span></span> | |
| <button id="addParticle" class="bg-purple-600 hover:bg-purple-700 px-3 py-1 rounded-lg text-sm transition-colors"> | |
| <i data-feather="plus"></i> | |
| </button> | |
| </div> | |
| <button id="reset" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg text-sm transition-colors flex items-center"> | |
| <i data-feather="refresh-cw" class="w-4 h-4 mr-1"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Canvas --> | |
| <canvas id="particleCanvas" class="w-full h-screen"></canvas> | |
| <!-- Controls Panel --> | |
| <div class="absolute bottom-0 left-0 w-full z-20"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <div class="bg-gray-800 bg-opacity-80 backdrop-blur-sm rounded-xl p-6 max-w-md mx-auto"> | |
| <h2 class="text-xl font-semibold mb-4 text-purple-300 flex items-center"> | |
| <i data-feather="settings" class="mr-2"></i> Controls | |
| </h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium mb-2">Minimum Distance</label> | |
| <input type="range" id="minDist" min="10" max="200" value="50" class="w-full accent-purple-500"> | |
| <div class="flex justify-between text-xs text-gray-400"> | |
| <span>10</span> | |
| <span id="minDistValue">50</span> | |
| <span>200</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-2">Maximum Distance</label> | |
| <input type="range" id="maxDist" min="100" max="500" value="300" class="w-full accent-purple-500"> | |
| <div class="flex justify-between text-xs text-gray-400"> | |
| <span>100</span> | |
| <span id="maxDistValue">300</span> | |
| <span>500</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium mb-2">Attraction Force</label> | |
| <input type="range" id="force" min="0.1" max="2" step="0.1" value="0.5" class="w-full accent-purple-500"> | |
| <div class="flex justify-between text-xs text-gray-400"> | |
| <span>0.1</span> | |
| <span id="forceValue">0.5</span> | |
| <span>2.0</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-4 p-3 bg-gray-700 rounded-lg"> | |
| <p class="text-sm text-gray-300">Particles have values (2-100) and attract based on shared prime factors!</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Info Panel --> | |
| <div class="absolute top-20 right-4 z-20"> | |
| <div class="bg-gray-800 bg-opacity-80 backdrop-blur-sm rounded-xl p-4 max-w-xs"> | |
| <h3 class="text-lg font-semibold mb-2 text-purple-300">How it works</h3> | |
| <ul class="text-sm text-gray-300 space-y-1"> | |
| <li>• Particles have unique values (2-100)</li> | |
| <li>• They attract when sharing prime factors</li> | |
| <li>• Phase affects attraction/repulsion</li> | |
| <li>• Click anywhere to add particles</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('particleCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Set canvas size | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| // Prime factor calculation | |
| function getPrimeFactors(num) { | |
| const factors = []; | |
| let divisor = 2; | |
| while (num > 1) { | |
| if (num % divisor === 0) { | |
| factors.push(divisor); | |
| num /= divisor; | |
| } else { | |
| divisor++; | |
| } | |
| } | |
| return [...new Set(factors)]; // Unique factors only | |
| } | |
| // Particle system | |
| class Particle { | |
| constructor(x, y) { | |
| this.x = x || Math.random() * canvas.width; | |
| this.y = y || Math.random() * canvas.height; | |
| this.vx = (Math.random() - 0.5) * 2; | |
| this.vy = (Math.random() - 0.5) * 2; | |
| this.radius = 4 + Math.random() * 4; | |
| this.phase = Math.random() * 2 * Math.PI; | |
| this.hue = (this.phase / (2 * Math.PI)) * 360; | |
| this.value = Math.floor(Math.random() * 99) + 2; // Values 2-100 | |
| this.primeFactors = getPrimeFactors(this.value); | |
| this.trail = []; | |
| this.maxTrailLength = 10; | |
| } | |
| update(particles, minDist, maxDist, force) { | |
| // Apply phase influence from nearby particles | |
| let totalPhase = 0; | |
| let validParticles = 0; | |
| let totalDistance = 0; | |
| particles.forEach(other => { | |
| if (other !== this) { | |
| const dx = other.x - this.x; | |
| const dy = other.y - this.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance >= minDist && distance <= maxDist) { | |
| totalPhase += other.phase; | |
| totalDistance += distance; | |
| validParticles++; | |
| } | |
| } | |
| }); | |
| if (validParticles > 0) { | |
| const avgPhase = totalPhase / validParticles; | |
| const avgDistance = totalDistance / validParticles; | |
| const phaseInfluence = (validParticles * avgPhase) / avgDistance; | |
| this.phase = (this.phase + phaseInfluence * 0.001) % (2 * Math.PI); | |
| this.hue = (this.phase / (2 * Math.PI)) * 360; | |
| } | |
| // Apply attraction/repulsion based on phase | |
| particles.forEach(other => { | |
| if (other !== this) { | |
| const dx = other.x - this.x; | |
| const dy = other.y - this.y; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| if (distance > 0 && distance < maxDist) { | |
| // Phase-based force | |
| let phaseForce = 0; | |
| if (this.phase < Math.PI) { | |
| phaseForce = 1 - (this.phase / Math.PI); // Decreasing attraction | |
| } else { | |
| phaseForce = -((this.phase - Math.PI) / Math.PI); // Increasing repulsion | |
| } | |
| const fx = (dx / distance) * force * phaseForce; | |
| const fy = (dy / distance) * force * phaseForce; | |
| this.vx += fx; | |
| this.vy += fy; | |
| // Prime factor attraction - stronger bond when particles share prime factors | |
| const sharedFactors = this.primeFactors.filter(factor => | |
| other.primeFactors.includes(factor) | |
| ); | |
| if (sharedFactors.length > 0) { | |
| const primeAttraction = sharedFactors.length * 0.05; | |
| const primeFx = (dx / distance) * primeAttraction; | |
| const primeFy = (dy / distance) * primeAttraction; | |
| this.vx += primeFx; | |
| this.vy += primeFy; | |
| } | |
| } | |
| } | |
| }); | |
| // Update position with velocity damping | |
| this.vx *= 0.98; | |
| this.vy *= 0.98; | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| // Boundary collision with energy loss | |
| if (this.x < 0 || this.x > canvas.width) { | |
| this.vx *= -0.8; | |
| this.x = Math.max(0, Math.min(canvas.width, this.x)); | |
| } | |
| if (this.y < 0 || this.y > canvas.height) { | |
| this.vy *= -0.8; | |
| this.y = Math.max(0, Math.min(canvas.height, this.y)); | |
| } | |
| // Update trail | |
| this.trail.push({x: this.x, y: this.y}); | |
| if (this.trail.length > this.maxTrailLength) { | |
| this.trail.shift(); | |
| } | |
| } | |
| draw() { | |
| // Draw trail | |
| for (let i = 0; i < this.trail.length - 1; i++) { | |
| const point = this.trail[i]; | |
| const nextPoint = this.trail[i + 1]; | |
| const alpha = i / this.trail.length; | |
| ctx.beginPath(); | |
| ctx.moveTo(point.x, point.y); | |
| ctx.lineTo(nextPoint.x, nextPoint.y); | |
| ctx.strokeStyle = `hsla(${this.hue}, 70%, 60%, ${alpha * 0.5})`; | |
| ctx.lineWidth = 1; | |
| ctx.stroke(); | |
| } | |
| // Draw particle | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI); | |
| ctx.fillStyle = `hsl(${this.hue}, 70%, 60%)`; | |
| ctx.fill(); | |
| // Draw glow effect | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.radius * 2, 0, 2 * Math.PI); | |
| const gradient = ctx.createRadialGradient( | |
| this.x, this.y, this.radius, | |
| this.x, this.y, this.radius * 2 | |
| ); | |
| gradient.addColorStop(0, `hsla(${this.hue}, 70%, 60%, 0.3)`); | |
| gradient.addColorStop(1, `hsla(${this.hue}, 70%, 60%, 0)`); | |
| ctx.fillStyle = gradient; | |
| ctx.fill(); | |
| // Draw phase indicator | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.radius + 3, 0, this.phase, false); | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.stroke(); | |
| // Draw value indicator | |
| ctx.fillStyle = 'white'; | |
| ctx.font = 'bold 12px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| ctx.fillText(this.value, this.x, this.y); | |
| } | |
| } | |
| // Initialize particles | |
| let particles = []; | |
| const initialCount = 50; | |
| for (let i = 0; i < initialCount; i++) { | |
| particles.push(new Particle()); | |
| } | |
| // Control variables | |
| let minDist = 50; | |
| let maxDist = 300; | |
| let force = 0.5; | |
| // Animation loop | |
| function animate() { | |
| // Fade effect for trail | |
| ctx.fillStyle = 'rgba(17, 24, 39, 0.1)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| particles.forEach(particle => { | |
| particle.update(particles, minDist, maxDist, force); | |
| particle.draw(); | |
| }); | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| // Event listeners for controls | |
| document.getElementById('addParticle').addEventListener('click', () => { | |
| particles.push(new Particle()); | |
| updateParticleCount(); | |
| }); | |
| document.getElementById('reset').addEventListener('click', () => { | |
| particles = []; | |
| for (let i = 0; i < initialCount; i++) { | |
| particles.push(new Particle()); | |
| } | |
| updateParticleCount(); | |
| }); | |
| document.getElementById('minDist').addEventListener('input', (e) => { | |
| minDist = parseInt(e.target.value); | |
| document.getElementById('minDistValue').textContent = minDist; | |
| }); | |
| document.getElementById('maxDist').addEventListener('input', (e) => { | |
| maxDist = parseInt(e.target.value); | |
| document.getElementById('maxDistValue').textContent = maxDist; | |
| }); | |
| document.getElementById('force').addEventListener('input', (e) => { | |
| force = parseFloat(e.target.value); | |
| document.getElementById('forceValue').textContent = force.toFixed(1); | |
| }); | |
| function updateParticleCount() { | |
| document.getElementById('particleCount').textContent = particles.length; | |
| } | |
| updateParticleCount(); | |
| // Add particles on click | |
| canvas.addEventListener('click', (e) => { | |
| const rect = canvas.getBoundingClientRect(); | |
| const x = e.clientX - rect.left; | |
| const y = e.clientY - rect.top; | |
| particles.push(new Particle(x, y)); | |
| updateParticleCount(); | |
| }); | |
| // Initialize feather icons | |
| feather.replace(); | |
| </script> | |
| </body> | |
| </html> | |