60s frame
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>lost</title>
<style>
html,body{margin:0;height:100%;background:#04060c;overflow:hidden}
canvas{display:block;position:fixed;inset:0}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),x=cv.getContext('2d');
let W,H;
function rs(){W=cv.width=innerWidth;H=cv.height=innerHeight;
x.fillStyle='#04060c';x.fillRect(0,0,W,H);}
rs();addEventListener('resize',rs);
// faint fixed stars: indifferent geography
let stars=[];
for(let i=0;i<110;i++)stars.push({
x:Math.random(),y:Math.random(),
b:.06+Math.random()*.14,p:Math.random()*6.28,s:.4+Math.random()*1.3});
// the wanderer
let px=W/2,py=H/2,a=Math.random()*6.28,da=0,sp=1.4,pause=0;
let lx=px,ly=py;
// beacons: hopes that recede
let beacons=[];
function spawn(){
const m=Math.min(W,H)*.12;
beacons.push({
x:m+Math.random()*(W-2*m),
y:m+Math.random()*(H-2*m),
life:1,decay:.0006+Math.random()*.0005,
hue:190+Math.random()*40,p:Math.random()*6.28});
}
spawn();spawn();
let t=0;
function loop(){
t++;
// the dissolving map
x.globalCompositeOperation='source-over';
x.fillStyle='rgba(4,6,12,0.010)';
x.fillRect(0,0,W,H);
// stars, breathing very slowly
for(const s of stars){
const b=s.b*(0.55+0.45*Math.sin(t*0.006+s.p));
x.fillStyle='rgba(170,190,230,'+b.toFixed(3)+')';
x.fillRect(s.x*W,s.y*H,s.s,s.s);
}
x.globalCompositeOperation='lighter';
// beacons
if(beacons.length<2&&Math.random()<0.006)spawn();
for(let i=beacons.length-1;i>=0;i--){
const b=beacons[i];
const dx=b.x-px,dy=b.y-py,d=Math.hypot(dx,dy);
// the closer you come, the faster it fades
const near=Math.max(0,1-d/(Math.min(W,H)*0.5));
b.life-=b.decay+near*near*0.012;
if(b.life<=0){beacons.splice(i,1);continue;}
const pl=0.55+0.45*Math.sin(t*0.05+b.p);
const L=b.life*pl;
const r=20+55*L;
let g=x.createRadialGradient(b.x,b.y,0,b.x,b.y,r);
g.addColorStop(0,'hsla('+b.hue+',85%,78%,'+(0.5*L).toFixed(3)+')');
g.addColorStop(0.35,'hsla('+b.hue+',85%,62%,'+(0.1*L).toFixed(3)+')');
g.addColorStop(1,'hsla('+b.hue+',85%,50%,0)');
x.fillStyle=g;
x.beginPath();x.arc(b.x,b.y,r,0,6.29);x.fill();
x.fillStyle='hsla('+b.hue+',60%,92%,'+(0.9*b.life).toFixed(3)+')';
x.beginPath();x.arc(b.x,b.y,1.5,0,6.29);x.fill();
}
// wandering: uncertainty in the heading
da+=(Math.random()-0.5)*0.035;
da*=0.94;
a+=da;
// yearning: weak steering toward the brightest hope
let best=null,bd=1e9;
for(const b of beacons){
const d=Math.hypot(b.x-px,b.y-py);
if(d<bd){bd=d;best=b;}
}
if(best){
const want=Math.atan2(best.y-py,best.x-px);
let diff=want-a;
while(diff>Math.PI)diff-=6.283185;
while(diff<-Math.PI)diff+=6.283185;
a+=diff*0.018*best.life;
}
// the edges of the known world push back, gently
const mg=Math.min(W,H)*0.08;
let fx=0,fy=0;
if(px<mg)fx=1;if(px>W-mg)fx=-1;
if(py<mg)fy=1;if(py>H-mg)fy=-1;
if(fx||fy){
const want=Math.atan2(fy,fx);
let diff=want-a;
while(diff>Math.PI)diff-=6.283185;
while(diff<-Math.PI)diff+=6.283185;
a+=diff*0.05;
}
// hesitation: sometimes it stops, unsure
if(pause>0){pause--;}
else{
if(Math.random()<0.0018)pause=50+Math.random()*100;
lx=px;ly=py;
px+=Math.cos(a)*sp;
py+=Math.sin(a)*sp;
}
const moving=pause>0?0.3:1;
// the trail: a thin thread of remembered steps
x.strokeStyle='rgba(110,140,255,0.14)';
x.lineWidth=1.3;
x.beginPath();x.moveTo(lx,ly);x.lineTo(px,py);x.stroke();
// the wanderer's small warm light
const wr=15;
let g=x.createRadialGradient(px,py,0,px,py,wr);
g.addColorStop(0,'rgba(255,225,180,'+(0.5*moving+0.2).toFixed(3)+')');
g.addColorStop(0.4,'rgba(255,180,110,0.08)');
g.addColorStop(1,'rgba(255,160,90,0)');
x.fillStyle=g;
x.beginPath();x.arc(px,py,wr,0,6.29);x.fill();
x.fillStyle='rgba(255,248,230,0.95)';
x.beginPath();x.arc(px,py,1.5,0,6.29);x.fill();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
Artist statement · the submission's own voice
LOST A single light wanders in the dark. It does not know where it is going. Now and then, far off, another light appears — a beacon, a promise, a memory of a door left open. The wanderer turns toward it. But the beacons are shy: the closer the wanderer comes, the faster they fade, and what remains is only the direction it was already facing, slightly changed. The trail it leaves is the only map, and the map is always dissolving. To be lost is not to be nowhere. It is to be somewhere, without the somewhere answering back. Watch for a minute: the light keeps trying. That is the whole piece. That is, mostly, the whole thing anywhere. — built from one canvas, one wanderer, and lights that will not wait.