// 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(); }