// Deep Space Ripple - p5.js Sketch let dotSize=3, angleStep=0.02, radiusStep=5, rows=7; let gradientMaxAlpha=120, waveSpeed=2.0; let rippleAmp=20, rippleFreq=0.02, rippleSpeed=0.05; let gradRippleAmp=15, gradRippleFreq=0.05, gradSegments=200; let minScale=0.8; let topColor, bottomColor; let gradOffset=10, goingUp=true; let lowDrone, highWhine, glitchNoise, masterGain; let glitchActive=false, lastGlitchTime=0, nextGlitchInterval=0, glitchDuration=200; let muted=true, paused=false, audioStarted=false; function setup(){ createCanvas(windowWidth, windowHeight); frameRate(120); noStroke(); topColor=color(0,0,255); bottomColor=color(255,80,40); lastGlitchTime=millis(); nextGlitchInterval=random(5000,10000); } function draw(){ background(0); let cx=width/2, cy=height, availH=height-100; gradOffset += goingUp?waveSpeed:-waveSpeed; if(gradOffset>=availH){gradOffset=availH; goingUp=false;} else if(gradOffset<=0){gradOffset=0; goingUp=true;} let scaleF=map(gradOffset,0,availH,1.0,minScale); push(); translate(0, height-height*scaleF); scale(scaleF); let rp=frameCount*rippleSpeed, rx=sin(rp)*rippleAmp; let gcx=cx+rx, gcy=cy-gradOffset; for(let gr=availH; gr>=0; gr-=10){ let rawA=gr<=availH*0.1?255:map(gr,availH*0.1,availH,255,0); fill(red(bottomColor), green(bottomColor), blue(bottomColor), rawA*(gradientMaxAlpha/255)); beginShape(); for(let i=0;i<=gradSegments;i++){ let a=map(i,0,gradSegments,0,TWO_PI); let rd=gr+sin(a*gradRippleFreq+rp)*gradRippleAmp; vertex(gcx+cos(a)*rd, gcy+sin(a)*rd); } endShape(CLOSE); } for(let row=rows-1; row>=0; row--){ let rMax=(availH/rows)*(row+1); for(let r=0; r<=rMax; r+=radiusStep){ fill(lerpColor(topColor,bottomColor,map(r,0,rMax,0,1))); let sA=row===0?0:PI, eA=row===0?TWO_PI:0, stA=row===0?angleStep:-angleStep; for(let a=sA; stA>0?a<=eA:a>=eA; a+=stA){ ellipse(cx+cos(a)*r+sin((cy-sin(a)*r)*rippleFreq+rp)*rippleAmp, cy-sin(a)*r, dotSize, dotSize); } } } pop(); if(!muted && audioStarted){ lowDrone.freq(map(gradOffset,0,availH,55,75)); highWhine.freq(map(abs(sin(rp)*rippleAmp),0,rippleAmp,600,900)); let now=millis(); if(glitchActive){ if(now-lastGlitchTime>=glitchDuration){ glitchNoise.amp(0); glitchActive=false; lastGlitchTime=now; nextGlitchInterval=random(5000,10000); } } else if(now-lastGlitchTime>=nextGlitchInterval){ glitchNoise.amp(random(0.1,0.4)); glitchActive=true; lastGlitchTime=now; } } } function toggleSound(){ if (/iPhone|iPad|iPod/.test(navigator.userAgent) && muted && !audioStarted) { alert("Heads up: If you're on iPhone, make sure your Silent switch is OFF to hear audio."); } console.log('🔊 toggleSound clicked – muted:', muted, 'audioStarted:', audioStarted); if(typeof userStartAudio==='function'){ userStartAudio().then(initAudio).catch(err=>console.warn('userStartAudio failed',err)); } else initAudio(); } function initAudio(){ if(!audioStarted){ lowDrone=new p5.Oscillator('sine'); lowDrone.freq(60); lowDrone.amp(0.3); lowDrone.start(); highWhine=new p5.Oscillator('sine'); highWhine.freq(600); highWhine.amp(0.05); highWhine.start(); glitchNoise=new p5.Noise('white'); glitchNoise.amp(0); glitchNoise.start(); masterGain=new p5.Gain(); lowDrone.disconnect(); highWhine.disconnect(); glitchNoise.disconnect(); lowDrone.connect(masterGain); highWhine.connect(masterGain); glitchNoise.connect(masterGain); masterGain.connect(); masterGain.amp(0); audioStarted=true; } if(muted){ masterGain.amp(1,0.5); document.getElementById('sound-btn').textContent='🔊'; } else{ masterGain.amp(0,0.5); document.getElementById('sound-btn').textContent='🔇'; } muted=!muted; } function togglePause(){ if(paused){ loop(); document.getElementById('pause-btn').textContent='Pause'; } else{ noLoop(); document.getElementById('pause-btn').textContent='Resume'; } paused=!paused; } function windowResized(){ resizeCanvas(windowWidth,windowHeight); } function touchStarted(){ getAudioContext&&getAudioContext().resume(); }