Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Psychedelic Vortex Generator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| background: #000; | |
| touch-action: none; | |
| } | |
| #controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| background: rgba(0,0,0,0.5); | |
| padding: 10px; | |
| border-radius: 8px; | |
| color: white; | |
| font-family: monospace; | |
| } | |
| .control-item { | |
| margin: 8px 0; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| #toggleControls { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| background: rgba(0,0,0,0.5); | |
| color: white; | |
| border: none; | |
| padding: 8px 12px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| } | |
| #audioToggle { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| background: rgba(0,0,0,0.5); | |
| color: white; | |
| border: none; | |
| padding: 8px 12px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| } | |
| #title { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| color: white; | |
| font-family: monospace; | |
| font-size: 24px; | |
| text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #e60073, 0 0 40px #e60073; | |
| opacity: 0.8; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="title">PSYCHEDELIC VORTEX GENERATOR</div> | |
| <button id="audioToggle">ENABLE AUDIO</button> | |
| <div id="controls" class="hidden"> | |
| <div class="control-item"> | |
| <label for="particleCount">Particle Count: </label> | |
| <input type="range" id="particleCount" min="100" max="5000" value="2000" step="100"> | |
| <span id="particleCountValue">2000</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="vortexStrength">Vortex Strength: </label> | |
| <input type="range" id="vortexStrength" min="0" max="2" value="0.5" step="0.01"> | |
| <span id="vortexStrengthValue">0.5</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="noiseScale">Noise Scale: </label> | |
| <input type="range" id="noiseScale" min="0.001" max="0.05" value="0.01" step="0.001"> | |
| <span id="noiseScaleValue">0.01</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="colorSpeed">Color Speed: </label> | |
| <input type="range" id="colorSpeed" min="0" max="0.1" value="0.02" step="0.001"> | |
| <span id="colorSpeedValue">0.02</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="trailOpacity">Trail Opacity: </label> | |
| <input type="range" id="trailOpacity" min="0" max="50" value="10" step="1"> | |
| <span id="trailOpacityValue">10</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="particleSize">Particle Size: </label> | |
| <input type="range" id="particleSize" min="0.5" max="5" value="2" step="0.1"> | |
| <span id="particleSizeValue">2</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="mouseInfluence">Mouse Influence: </label> | |
| <input type="range" id="mouseInfluence" min="0" max="2" value="1" step="0.1"> | |
| <span id="mouseInfluenceValue">1</span> | |
| </div> | |
| <div class="control-item"> | |
| <label for="audioSensitivity">Audio Sensitivity: </label> | |
| <input type="range" id="audioSensitivity" min="0" max="500" value="100" step="10"> | |
| <span id="audioSensitivityValue">100</span> | |
| </div> | |
| </div> | |
| <button id="toggleControls">TOGGLE CONTROLS</button> | |
| <script> | |
| // Main sketch | |
| let particles = []; | |
| let audioContext; | |
| let analyser; | |
| let microphone; | |
| let audioEnabled = false; | |
| let dataArray; | |
| let fft; | |
| let bassLevel = 0; | |
| let midLevel = 0; | |
| let highLevel = 0; | |
| let hue = 0; | |
| // Parameters with default values | |
| let params = { | |
| particleCount: 2000, | |
| vortexStrength: 0.5, | |
| noiseScale: 0.01, | |
| colorSpeed: 0.02, | |
| trailOpacity: 10, | |
| particleSize: 2, | |
| mouseInfluence: 1, | |
| audioSensitivity: 100 | |
| }; | |
| function setup() { | |
| createCanvas(windowWidth, windowHeight); | |
| colorMode(HSB, 360, 100, 100, 100); | |
| // Initialize particles | |
| initParticles(); | |
| // Setup UI event listeners | |
| setupUI(); | |
| } | |
| function initParticles() { | |
| particles = []; | |
| for (let i = 0; i < params.particleCount; i++) { | |
| particles.push(new Particle()); | |
| } | |
| } | |
| function setupUI() { | |
| // Slider controls | |
| document.getElementById('particleCount').addEventListener('input', function() { | |
| params.particleCount = parseInt(this.value); | |
| document.getElementById('particleCountValue').textContent = params.particleCount; | |
| initParticles(); | |
| }); | |
| document.getElementById('vortexStrength').addEventListener('input', function() { | |
| params.vortexStrength = parseFloat(this.value); | |
| document.getElementById('vortexStrengthValue').textContent = params.vortexStrength; | |
| }); | |
| document.getElementById('noiseScale').addEventListener('input', function() { | |
| params.noiseScale = parseFloat(this.value); | |
| document.getElementById('noiseScaleValue').textContent = params.noiseScale; | |
| }); | |
| document.getElementById('colorSpeed').addEventListener('input', function() { | |
| params.colorSpeed = parseFloat(this.value); | |
| document.getElementById('colorSpeedValue').textContent = params.colorSpeed; | |
| }); | |
| document.getElementById('trailOpacity').addEventListener('input', function() { | |
| params.trailOpacity = parseInt(this.value); | |
| document.getElementById('trailOpacityValue').textContent = params.trailOpacity; | |
| }); | |
| document.getElementById('particleSize').addEventListener('input', function() { | |
| params.particleSize = parseFloat(this.value); | |
| document.getElementById('particleSizeValue').textContent = params.particleSize; | |
| }); | |
| document.getElementById('mouseInfluence').addEventListener('input', function() { | |
| params.mouseInfluence = parseFloat(this.value); | |
| document.getElementById('mouseInfluenceValue').textContent = params.mouseInfluence; | |
| }); | |
| document.getElementById('audioSensitivity').addEventListener('input', function() { | |
| params.audioSensitivity = parseInt(this.value); | |
| document.getElementById('audioSensitivityValue').textContent = params.audioSensitivity; | |
| }); | |
| // Toggle controls button | |
| document.getElementById('toggleControls').addEventListener('click', function() { | |
| const controls = document.getElementById('controls'); | |
| controls.classList.toggle('hidden'); | |
| }); | |
| // Audio toggle button | |
| document.getElementById('audioToggle').addEventListener('click', toggleAudio); | |
| } | |
| function toggleAudio() { | |
| if (!audioEnabled) { | |
| // Initialize audio context | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 256; | |
| // Get microphone input | |
| navigator.mediaDevices.getUserMedia({ audio: true, video: false }) | |
| .then(function(stream) { | |
| microphone = audioContext.createMediaStreamSource(stream); | |
| microphone.connect(analyser); | |
| dataArray = new Uint8Array(analyser.frequencyBinCount); | |
| fft = new p5.FFT(); | |
| fft.setInput(microphone); | |
| audioEnabled = true; | |
| document.getElementById('audioToggle').textContent = 'DISABLE AUDIO'; | |
| }) | |
| .catch(function(err) { | |
| console.error('Error accessing microphone:', err); | |
| alert('Could not access microphone. Please check permissions.'); | |
| }); | |
| } else { | |
| // Disable audio | |
| if (microphone) { | |
| microphone.disconnect(); | |
| } | |
| audioEnabled = false; | |
| document.getElementById('audioToggle').textContent = 'ENABLE AUDIO'; | |
| } | |
| } | |
| function draw() { | |
| // Update color cycle | |
| hue = (hue + params.colorSpeed) % 360; | |
| // Background with trail effect | |
| background(0, 0, 0, params.trailOpacity); | |
| // Update audio analysis if enabled | |
| if (audioEnabled) { | |
| updateAudioAnalysis(); | |
| } | |
| // Draw center vortex | |
| drawVortex(); | |
| // Update and draw particles | |
| for (let particle of particles) { | |
| particle.update(); | |
| particle.display(); | |
| } | |
| // Draw interactive elements | |
| drawInteractiveElements(); | |
| } | |
| function updateAudioAnalysis() { | |
| // Get frequency data | |
| analyser.getByteFrequencyData(dataArray); | |
| // Calculate frequency ranges | |
| let bassSum = 0; | |
| let midSum = 0; | |
| let highSum = 0; | |
| for (let i = 0; i < dataArray.length; i++) { | |
| if (i < 5) { | |
| bassSum += dataArray[i]; | |
| } else if (i < 15) { | |
| midSum += dataArray[i]; | |
| } else { | |
| highSum += dataArray[i]; | |
| } | |
| } | |
| bassLevel = bassSum / 5 / 255; | |
| midLevel = midSum / 10 / 255; | |
| highLevel = highSum / (dataArray.length - 15) / 255; | |
| } | |
| function drawVortex() { | |
| push(); | |
| translate(width/2, height/2); | |
| // Base vortex effect | |
| let vortexSize = width * 0.4; | |
| let audioBoost = audioEnabled ? (1 + bassLevel * 0.5) : 1; | |
| for (let i = 0; i < 360; i += 5) { | |
| let angle = radians(i); | |
| let dynamicSize = vortexSize * (1 + 0.2 * sin(millis() * 0.001 + i * 0.1)) * audioBoost; | |
| let x1 = cos(angle) * dynamicSize * 0.5; | |
| let y1 = sin(angle) * dynamicSize * 0.5; | |
| let x2 = cos(angle) * dynamicSize; | |
| let y2 = sin(angle) * dynamicSize; | |
| let col = color((hue + i) % 360, 80, 90, 30); | |
| stroke(col); | |
| strokeWeight(2); | |
| line(x1, y1, x2, y2); | |
| // Add some sparkles | |
| if (i % 30 === 0) { | |
| let sparkleSize = 3 + 5 * (0.5 + 0.5 * sin(millis() * 0.005 + i)); | |
| fill(col); | |
| noStroke(); | |
| ellipse(x2, y2, sparkleSize, sparkleSize); | |
| } | |
| } | |
| pop(); | |
| } | |
| function drawInteractiveElements() { | |
| // Draw mouse influence area | |
| if (mouseIsPressed) { | |
| let pulseSize = 50 + 20 * sin(millis() * 0.01); | |
| let col = color((hue + 180) % 360, 90, 100, 20); | |
| fill(col); | |
| noStroke(); | |
| ellipse(mouseX, mouseY, pulseSize, pulseSize); | |
| } | |
| // Draw audio visualization if enabled | |
| if (audioEnabled) { | |
| drawAudioVisualization(); | |
| } | |
| } | |
| function drawAudioVisualization() { | |
| push(); | |
| translate(width/2, height/2); | |
| // Draw frequency bars | |
| let barWidth = width / (dataArray.length / 2); | |
| for (let i = 0; i < dataArray.length / 2; i++) { | |
| let barHeight = dataArray[i] * params.audioSensitivity / 255; | |
| let col = color((hue + i * 5) % 360, 80, 90, 50); | |
| fill(col); | |
| noStroke(); | |
| rectMode(CENTER); | |
| rect( | |
| (i - dataArray.length / 4) * barWidth, | |
| height * 0.4, | |
| barWidth * 0.8, | |
| -barHeight | |
| ); | |
| } | |
| pop(); | |
| } | |
| function windowResized() { | |
| resizeCanvas(windowWidth, windowHeight); | |
| } | |
| // Particle class | |
| class Particle { | |
| constructor() { | |
| this.reset(); | |
| this.velocity = p5.Vector.random2D().mult(random(0.5, 2)); | |
| this.size = random(params.particleSize * 0.5, params.particleSize * 1.5); | |
| this.hueOffset = random(360); | |
| this.life = random(100, 200); | |
| this.age = 0; | |
| this.z = random(-100, 100); | |
| } | |
| reset() { | |
| this.position = createVector(random(width), random(height)); | |
| this.velocity = p5.Vector.random2D().mult(random(0.5, 2)); | |
| this.acceleration = createVector(0, 0); | |
| this.size = random(params.particleSize * 0.5, params.particleSize * 1.5); | |
| this.hueOffset = random(360); | |
| this.life = random(100, 200); | |
| this.age = 0; | |
| this.z = random(-100, 100); | |
| } | |
| update() { | |
| this.age++; | |
| if (this.age > this.life) { | |
| this.reset(); | |
| this.age = 0; | |
| } | |
| // Apply vortex force | |
| let center = createVector(width/2, height/2); | |
| let toCenter = p5.Vector.sub(center, this.position); | |
| let distance = toCenter.mag(); | |
| toCenter.normalize(); | |
| // Vortex effect with audio modulation | |
| let audioMod = audioEnabled ? (1 + bassLevel * 0.5) : 1; | |
| let vortexForce = toCenter.copy(); | |
| vortexForce.rotate(HALF_PI); | |
| vortexForce.mult(params.vortexStrength * audioMod * (1 + 0.2 * sin(distance * 0.01 - millis() * 0.001))); | |
| vortexForce.mult(map(distance, 0, width/2, 2, 0.1)); | |
| // Apply noise force | |
| let noiseForce = createVector( | |
| noise(this.position.x * params.noiseScale, this.position.y * params.noiseScale, millis() * 0.0001) - 0.5, | |
| noise(this.position.y * params.noiseScale, millis() * 0.0001, this.position.x * params.noiseScale) - 0.5 | |
| ); | |
| noiseForce.mult(0.5); | |
| // Apply mouse influence if mouse is pressed | |
| let mouseForce = createVector(0, 0); | |
| if (mouseIsPressed) { | |
| let toMouse = p5.Vector.sub(createVector(mouseX, mouseY), this.position); | |
| let mouseDist = toMouse.mag(); | |
| if (mouseDist < 200) { | |
| toMouse.normalize(); | |
| mouseForce = toMouse.mult(params.mouseInfluence * map(mouseDist, 0, 200, 1, 0)); | |
| } | |
| } | |
| // Combine all forces | |
| this.acceleration.add(vortexForce); | |
| this.acceleration.add(noiseForce); | |
| this.acceleration.add(mouseForce); | |
| // Apply audio forces if enabled | |
| if (audioEnabled) { | |
| // Bass pushes particles outward | |
| let audioOutwardForce = toCenter.copy(); | |
| audioOutwardForce.mult(-bassLevel * 0.5); | |
| // High frequencies add randomness | |
| let audioRandomForce = p5.Vector.random2D().mult(highLevel * 0.3); | |
| this.acceleration.add(audioOutwardForce); | |
| this.acceleration.add(audioRandomForce); | |
| } | |
| // Update physics | |
| this.velocity.add(this.acceleration); | |
| this.velocity.limit(3); | |
| this.position.add(this.velocity); | |
| this.acceleration.mult(0); | |
| // Wrap around edges | |
| if (this.position.x < 0) this.position.x = width; | |
| if (this.position.x > width) this.position.x = 0; | |
| if (this.position.y < 0) this.position.y = height; | |
| if (this.position.y > height) this.position.y = 0; | |
| // Z-depth movement | |
| this.z += random(-0.5, 0.5); | |
| this.z = constrain(this.z, -100, 100); | |
| } | |
| display() { | |
| let distanceToCenter = dist(this.position.x, this.position.y, width/2, height/2); | |
| let distanceFactor = map(distanceToCenter, 0, width/2, 0.3, 1); | |
| // Calculate color with audio modulation | |
| let particleHue = (hue + this.hueOffset + (audioEnabled ? midLevel * 50 : 0)) % 360; | |
| let saturation = map(distanceToCenter, 0, width/2, 30, 90); | |
| let brightness = 70 + (audioEnabled ? highLevel * 30 : 0); | |
| let alpha = map(this.age, 0, this.life, 30, 80) * distanceFactor; | |
| // Size with audio modulation | |
| let displaySize = this.size * (1 + (audioEnabled ? bassLevel * 0.5 : 0)); | |
| // Draw particle | |
| noStroke(); | |
| fill(particleHue, saturation, brightness, alpha); | |
| // Add some depth with z position | |
| let zFactor = map(this.z, -100, 100, 0.7, 1.3); | |
| ellipse( | |
| this.position.x, | |
| this.position.y, | |
| displaySize * zFactor, | |
| displaySize * zFactor | |
| ); | |
| // Add glow effect for some particles | |
| if (random() < 0.1) { | |
| fill(particleHue, saturation, brightness, alpha * 0.3); | |
| ellipse( | |
| this.position.x, | |
| this.position.y, | |
| displaySize * zFactor * 3, | |
| displaySize * zFactor * 3 | |
| ); | |
| } | |
| } | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=maydayjeffk/trippy-deepsite" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |