SOURCE / PINNED RELEASE
Made of little things.
Drone Zone — deep field
- Release
- 578f277a8be1…
- Author-recorded commit
- a96742b428de…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-9753e6cf3ec
Archive hash verified: f7f3bf8ff3482551…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
interface Burst { start:number; duration:number; row:number; direction:number }
/** Final optical treatment of the complete app surface, including the live UI.
* The scene supplies the clock: no separate animation loop, timers or authority.
* Expensive displacement/color separation runs only during a short tracking slip. */
export class TapeEffect {
private amount=.45;
private time=0;
private active=false;
private nextBurst=2.4;
private burst:Burst|null=null;
private seed=94103;
private noiseFrame=-1;
private disposed=false;
private overlay=document.getElementById('tape-overlay')!;
private noise=document.getElementById('tape-noise')!;
private roll=document.getElementById('tape-roll')!;
private dropout=document.getElementById('tape-dropout')!;
private slider=document.getElementById('tape-wear') as HTMLInputElement;
private readout=document.getElementById('tape-amount')!;
private pulseButton=document.getElementById('tape-pulse') as HTMLButtonElement;
private bandA=document.getElementById('tape-band-a')!;
private bandB=document.getElementById('tape-band-b')!;
private displacement=document.getElementById('tape-displacement')!;
private red=document.getElementById('tape-red')!;
private cyan=document.getElementById('tape-cyan')!;
constructor(private root:HTMLElement) {
// A single embedded grain tile, translated at low frequency instead of
// regenerating a frame-sized texture on every draw.
const canvas=document.createElement('canvas');canvas.width=128;canvas.height=128;
const ctx=canvas.getContext('2d')!;const data=ctx.createImageData(128,128);
for(let i=0;i<data.data.length;i+=4) {
const luminance=Math.floor(this.random()*256);
data.data[i]=data.data[i+1]=data.data[i+2]=luminance;data.data[i+3]=180;
}
ctx.putImageData(data,0,0);this.noise.style.backgroundImage=`url("${canvas.toDataURL()}")`;
this.setAmount(this.amount);
}
private random() {this.seed=(this.seed*1664525+1013904223)>>>0;return this.seed/4294967296;}
setAmount(value:number) {
if(this.disposed)return;
const previous=this.amount;this.amount=Number.isFinite(value)?Math.max(0,Math.min(1,value)):.45;
if(previous===0&&this.amount>0)this.nextBurst=this.time+2.4;
if(this.amount===0)this.burst=null;
this.slider.value=String(Math.round(this.amount*100));this.readout.textContent=this.amount?`${Math.round(this.amount*100)}%`:'OFF';
this.root.style.setProperty('--tape-amount',String(this.amount));
this.root.style.setProperty('--tape-saturation',String(1-this.amount*.12));
this.render(this.time,this.active);
}
pulse() {
if(this.disposed||!this.active||this.amount===0||this.burst)return false;
this.burst={start:this.time,duration:.58,row:.2+this.random()*.55,direction:this.random()<.5?-1:1};
this.nextBurst=this.time+8+this.random()*10;this.render(this.time,this.active);return true;
}
render(time:number,active:boolean) {
if(this.disposed)return;
this.time=time;this.active=active;
const enabled=this.amount>0;
this.root.dataset.tapeEnabled=String(enabled);
this.root.dataset.tapePaused=String(!active);
this.overlay.hidden=!enabled;
if(!enabled||!active) {
this.burst=null;this.root.dataset.tapeGlitch='false';this.dropout.style.opacity='0';
this.pulseButton.disabled=true;this.pulseButton.title=enabled?'Resume motion to trigger a glitch':'Increase tape wear to trigger a glitch';return;
}
if(time>=this.nextBurst&&!this.burst)this.pulse();
const frame=Math.floor(time*10);
if(frame!==this.noiseFrame) {
this.noiseFrame=frame;this.noise.style.transform=`translate(${Math.floor(this.random()*80)-40}px,${Math.floor(this.random()*80)-40}px)`;
}
this.roll.style.transform=`translateY(${((time*.032)%1)*140-20}vh)`;
if(this.burst&&time-this.burst.start>=this.burst.duration)this.burst=null;
this.pulseButton.disabled=!!this.burst;this.pulseButton.title='Trigger a brief tracking slip';
this.root.dataset.tapeGlitch=String(!!this.burst);
if(!this.burst){this.dropout.style.opacity='0';return;}
const age=(time-this.burst.start)/this.burst.duration;
// A tracking kick followed by a chroma echo. Keeping the two full-surface
// filters in consecutive phases avoids an expensive chained raster pass.
const tracking=age<.42,phase=tracking?age/.42:(age-.42)/.58;
this.root.dataset.tapePhase=tracking?'tracking':'chroma';
const envelope=Math.sin(Math.PI*phase),strength=this.amount*envelope;
const width=this.root.clientWidth,height=this.root.clientHeight;
const row=(this.burst.row+age*.17)*height;
this.bandA.setAttribute('x','-32');this.bandA.setAttribute('width',String(width+64));
this.bandA.setAttribute('y',String(row));this.bandA.setAttribute('height',String(3+height*.022));
this.bandB.setAttribute('x','-32');this.bandB.setAttribute('width',String(width+64));
this.bandB.setAttribute('y',String((this.burst.row+.22-age*.06)*height));this.bandB.setAttribute('height',String(2+height*.006));
this.displacement.setAttribute('scale',String((28+Math.sin(age*17)*8)*strength*this.burst.direction));
this.red.setAttribute('dx',String(5*strength));this.cyan.setAttribute('dx',String(-3*strength));
this.dropout.style.top=`${row}px`;this.dropout.style.opacity=String(envelope*.24);
}
destroy() {
this.disposed=true;this.burst=null;this.root.dataset.tapeEnabled='false';this.root.dataset.tapeGlitch='false';
this.overlay.hidden=true;this.noise.style.backgroundImage='none';
}
}