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.
import { config, themeGet, themeOnChanged, type Theme, type Subscription } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import { GalaxyField, observations } from './scene.js';
import { Radio } from './radio.js';
import { TapeEffect } from './tape.js';
import { subscribeSettings } from './napplet-settings.js';
import './styles.css';
const element=<T extends HTMLElement>(id:string)=>document.getElementById(id) as T;
const scene=new GalaxyField(element('universe'));
const tape=new TapeEffect(element('app'));
scene.onFrame=(time,active)=>tape.render(time,active);
const radio=new Radio();
const subscriptions:Subscription[]=[];
const events=new AbortController();
const listen=(id:string,action:()=>void)=>element(id).addEventListener('click',action,{signal:events.signal});
let selected=0,dead=false;
const discovered=new Set<number>();
function applyTheme(theme:Theme) {
if(dead)return;
const root=document.documentElement;
root.style.setProperty('--bg',theme.colors.background);root.style.setProperty('--fg',theme.colors.text);root.style.setProperty('--primary',theme.colors.primary);
root.style.colorScheme='light dark';
}
if(runtimeHasDomain('theme')) {
try{void themeGet().then(applyTheme).catch(()=>{});subscriptions.push(themeOnChanged(applyTheme));}catch{}
}
const settings=subscribeSettings(values=>{const {vhs,...field}=values;scene.setSettings(field);tape.setAmount(vhs);element<HTMLInputElement>('fog').checked=values.fog;element<HTMLInputElement>('distortion').checked=values.distortion;});
if(settings)subscriptions.push(settings);
if(runtimeHasDomain('config')){element('settings').hidden=false;listen('settings',()=>{try{config.openSettings();}catch{element('settings').textContent='Settings unavailable';}});}
scene.onChange=(zoom,x,y)=>{
element('zoom').textContent=`${zoom.toFixed(2)}×`;
const format=(value:number)=>`${value<0?'−':'+'}${Math.abs(value).toFixed(1).padStart(6,'0')}`;
element('position').textContent=`${format(x)} / ${format(y)}`;
};
function updateFrozen(frozen:boolean) {
const button=element('motion');button.setAttribute('aria-pressed',String(frozen));button.setAttribute('aria-label',frozen?'Resume motion':'Freeze motion');button.title=frozen?'Resume motion':'Freeze motion';button.textContent=frozen?'▷':'Ⅱ';
}
scene.onFrozen=updateFrozen;updateFrozen(scene.frozen);
function updateTour(touring:boolean) {
const button=element('tour');button.setAttribute('aria-pressed',String(touring));button.setAttribute('aria-label',touring?'Pause tour':'Resume tour');button.title=touring?'Pause tour':'Resume tour';
element('tour-label').textContent=touring?'TOUR / H01':'TOUR / RESUME';element('navigation-mode').textContent=touring?'AUTO / LONGITUDE HIGHWAY':'MANUAL / FREE FIELD';
element('app').dataset.touring=String(touring);
}
scene.onTour=updateTour;updateTour(scene.touring);listen('tour',()=>scene.toggleTour());
element('discovery-count').textContent=`00 / ${observations.length}`;
listen('motion',()=>scene.setFrozen(!scene.frozen));listen('home',()=>scene.reset());listen('zoom-in',()=>scene.magnify(1.12));listen('zoom-out',()=>scene.magnify(1/1.12));
function togglePanel(button:string,panel:string,otherButton:string,otherPanel:string) {
element(panel).hidden=!element(panel).hidden;element(button).setAttribute('aria-expanded',String(!element(panel).hidden));element(otherPanel).hidden=true;element(otherButton).setAttribute('aria-expanded','false');
}
element<HTMLInputElement>('tape-wear').addEventListener('input',()=>{tape.setAmount(Number(element<HTMLInputElement>('tape-wear').value)/100);scene.wake();},{signal:events.signal});
listen('tape-pulse',()=>{tape.pulse();scene.wake();});
listen('layers',()=>togglePanel('layers','options','help','guide'));listen('help',()=>togglePanel('help','guide','layers','options'));
for(const id of ['routes','annotations','fog','distortion']) element(id).addEventListener('change',()=>{
const checked=element<HTMLInputElement>(id).checked;
if(id==='routes')scene.routesVisible=checked;else if(id==='annotations')scene.annotationsVisible=checked;else scene.setSettings({[id]:checked});scene.wake();
},{signal:events.signal});
function showObservation(index:number,center=false) {
selected=(index+observations.length)%observations.length;discovered.add(selected);element('discovery-count').textContent=`${String(discovered.size).padStart(2,'0')} / ${observations.length}`;const observation=observations[selected];scene.select(selected,center);
element('observation').hidden=false;element('observation-id').textContent=`OBSERVATION ${String(selected+1).padStart(2,'0')}`;
element('observation-name').textContent=observation.name;element('observation-value').textContent=observation.value;element('observation-copy').textContent=observation.description;element('observation-count').textContent=`${selected+1} / ${observations.length}`;
}
scene.onSelect=index=>showObservation(index);
listen('inspect',()=>showObservation(selected,true));listen('previous',()=>showObservation(selected-1,true));listen('next',()=>showObservation(selected+1,true));
listen('close-observation',()=>{element('observation').hidden=true;scene.select(-1);element('inspect').focus();});
element('app').addEventListener('keydown',event=>{if(event.key==='Escape'){for(const panel of ['observation','options','guide'])element(panel).hidden=true;for(const button of ['layers','help'])element(button).setAttribute('aria-expanded','false');scene.select(-1);element('universe').focus();}},{signal:events.signal});
function clock(){if(!document.hidden)element('utc').textContent=`${new Date().toISOString().slice(11,19)} UTC`;}
clock();const clockTimer=window.setInterval(clock,1000);
element('universe').addEventListener('pointerdown',()=>{element('hint').style.opacity='0';},{signal:events.signal,once:true});
window.addEventListener('pagehide',()=>{dead=true;clearInterval(clockTimer);events.abort();subscriptions.forEach(sub=>sub.close());radio.destroy();tape.destroy();scene.destroy();},{once:true});