Nikon & Timelapse & RS3 mini abilised
${e.note?(`
${e.note}
`):''}
${e.meta?(`
${e.meta}
`):''}
`;
eventsList.appendChild(div);
});
// bind delete
eventsList.querySelectorAll('button[data-del]').forEach(b=>{
b.onclick = () => { events.splice(Number(b.dataset.del),1); renderEvents(); saveLocal(); redraw(); };
});
};
addEvtBtn.addEventListener('click', ()=>{
const type = evtType.value;
const title = evtTitle.value.trim();
const last = path[path.length-1]||{};
const base = {type, title, t: nowISO(), lat:last.lat, lon:last.lon};
if(type==='nikon'){
base.note = 'Fotohetk';
// attach current camera recommendation
base.meta = shutterHint.textContent.replace('Soovituslik ','');
} else if(type==='rs3'){
base.note = 'Gimbali liikumine';
base.meta = gimbalHint.textContent || '';
} else if(type==='poi'){
base.note = 'Huvipunkt';
} else {
base.note = prompt('Lisa lĂŒhike mĂ€rkus (valikuline):') || '';
}
events.push(base);
evtTitle.value = '';
renderEvents(); saveLocal(); redraw();
});
// Exports
const download = (name, mime, content) => {
const blob = new Blob([content], {type: mime});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = name;
document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
};
expGPX.onclick = () => {
const gpxPts = path.map(p=>`
${(p.alt!==null)?`${p.alt}`:''}`).join('');
const wpts = events.map(e=> (e.lat&&e.lon)? `
${(e.type||'evt')} - ${(e.title||'')}` : '').join('');
const gpx = `
${wpts}
RIDE${gpxPts}
`;
download('rada.gpx','application/gpx+xml',gpx);
};
expCSV.onclick = () => {
const header = 'time,lat,lon,alt_m,speed_mps,heading_deg\n';
const rows = path.map(p=>[p.t,p.lat,p.lon,(p.alt??''),(p.spd??''),(p.hdg??'')].join(',')).join('\n');
download('rada.csv','text/csv', header+rows);
};
expJSON.onclick = () => {
download('rada.json','application/json', JSON.stringify({path,events,totalDist}, null, 2));
};
// Nikon shutter advice
const recalcShutter = () => {
const crop = (camModel.value==='aps') ? 1.5 : 1.0;
const f = Math.max(1, Number(focal.value)||50);
const baseRule = 1 / (f * crop); // 1 / (f * crop)
const vrStops = vr.checked ? 2 : 0;
let minShutter = baseRule * Math.pow(2, vrStops); // VR lubab aeglasemalt
// Liikuva subjekti kompensatsioon (lihtne; 10 km/h ~ +1 stop, 20 ~ +2 stop jne.)
const v = Math.max(0, Number(subjectSpeed.value)||0);
const addStops = v>=1 ? Math.log2(1 + v/10) : 0; // pehme kasv
minShutter = minShutter * Math.pow(2, addStops); // kiirem = vÀiksem aeg (suurem 1/x)
const nice = shutterToString(minShutter);
shutterHint.innerHTML = `Soovituslik min. sÀriaeg:
${nice} (reegel 1/(f·crop), VR=${vr.checked?'+2':'0'} stoppi, liikumineâ${addStops.toFixed(1)} stoppi)`;
};
const shutterToString = (s) => {
// s on sekundites (nt 0.01s), vÀljund 1/100 s vÔi 0.5 s
const inv = 1/s;
if(inv > 1){ // 1/x
const steps = [1,1.3,1.6,2,2.5,3.2,4,5,6,8,10,13,16,20,25,32,40,50,64,80,100,125,160,200,250,320,400,500,640,800,1000,1250,1600,2000,2500,3200,4000,5000,6400,8000];
let best = 1; let diff = Infinity;
for(const t of steps){ const d = Math.abs(inv - t); if(d
el.addEventListener('input', recalcShutter));
sunny16.onclick = ()=>{ focal.value = focal.value||50; subjectSpeed.value = 0; recalcShutter(); alert('Sunny 16 meeldetuletus: f/16, ISO 100, sĂ€riaeg â 1/ISO (1/100 s).'); };
recalcShutter();
// Timelapse
const calcTL = () => {
const fps = Math.max(1, Number(tlFps.value)||24);
const frames = Math.max(2, Number(tlFrames.value)||480);
const interval = Math.max(1, Number(tlInterval.value)||5);
const shootSeconds = frames * interval;
tlDuration.value = fmt.time(shootSeconds) + ` â video pikkus ${(frames/fps).toFixed(1)} s`;
tlHint.textContent = `Soovi korral: 10 min vÔtet @${interval}s annab ${(600/interval*1).toFixed(0)} kaadrit = ${(600/interval/fps).toFixed(1)} s videot`;
};
recalcTL.addEventListener('click', calcTL);
calcTL();
// RS3 mini planner
const calcG = () => {
const deg = Math.max(1, Number(moveDeg.value)||90);
const dur = Math.max(1, Number(moveDur.value)||6);
const dps = (deg / dur).toFixed(1);
gimbalHint.innerHTML = `Soovitatav kiirus: ${dps} °/s (seadista RS3 mini custom speed nii, et pan/tilt vastaks sellele)`;
};
calcGimbal.addEventListener('click', calcG);
calcG();
// Controls
startBtn.onclick = startWatch;
pauseBtn.onclick = pauseWatch;
stopBtn.onclick = stopWatch;
// Load saved
loadLocal();
autosaveState.textContent = 'aktiivne';
})();