Use code with caution.
Primary Target Feature Type
None / clear bottom or expected structures
GBA (Golden Brown Algae)
Green aquatic plant / EWM candidate
Sediment plume / runoff entry
Shoreline alteration / construction debris
Septic indicator / abnormal green fringe
Unidentified anomaly / change flag
Extent / Visual Severity
Unable to assess
Isolated point / minor footprint
Localized cluster / intermittent
Continuous linear fringe along shore
Broad blanket patch / high density
Interpretation Confidence
High confidence
Moderate / ground validation ideal
Low / ambiguous visibility or reflection
Reviewer Observational Notes
Save Observation Record
Clear
Session Database Sync State (0)
Export CSV
Export JSON
Reset DB
Reviewer Recording Required
Reason for skipping structured observation entry:
// --- AUTOMATICALLY EMBEDDED CLOUDFLARE PUBLIC STORAGE PATH ---
const BASE_IMAGE_PATH = "r2.dev";
// --- AUTOMATED SEQUENCE GENERATION ENGINE ---
function generateSequence(prefix, startNum, endNum) {
const images = [];
for (let i = startNum; i <= endNum; i++) {
const filename = ${prefix}${i}.JPG;
images.push({
imageNumber: i,
filename: filename,
path: BASE_IMAGE_PATH + filename,
lat: "45.0512 N",
lon: "85.3214 W"
});
}
return images;
}
// Data directory mapping your two 2026 runs
const DATA_DICTIONARY = {
"2026-07-10": {
id: "2026-07-10",
shortName: "Jul 10, 2026 (G0586724-50)",
images: generateSequence("G0586", 6724, 6750)
},
"2026-09-17": {
id: "2026-09-17",
shortName: "Sep 17, 2026 (G0837964-91)",
images: generateSequence("G0837", 964, 991)
}
};
const state = {
activeRunKeyA: "2026-07-10",
activeRunKeyB: "2026-09-17",
indexA: 0,
indexB: 0,
visible: "A",
mode: "explore",
scale: 1,
fitScale: 1,
panX: 0,
panY: 0,
isDragging: false,
startX: 0,
startY: 0,
pendingAction: null,
observations: []
};
function $(id) { return document.getElementById(id); }
function escapeHtml(str) { return str.replace(/&/g, "&").replace(//g, ">"); }
function runA() { return DATA_DICTIONARY[state.activeRunKeyA]; }
function runB() { return DATA_DICTIONARY[state.activeRunKeyB]; }
function imgA() { return runA().images[state.indexA] || runA().images[0]; }
function imgB() { return runB().images[state.indexB] || runB().images[0]; }
function currentRun() { return state.visible === "A" ? runA() : runB(); }
function currentImg() { return state.visible === "A" ? imgA() : imgB(); }
function pairKey() { return ${runA().id}_${imgA().imageNumber}__${runB().id}_${imgB().imageNumber}; }
function imageMeta(run, img) {
if (!img) return "No metadata.";
return
${img.filename}Frame: ${run.images.indexOf(img) + 1} of ${run.images.length}
GPS Baseline: ${img.lat}, ${img.lon};
}
function baseRecord() {
return {
pair_key: pairKey(),
run_a_id: runA().id,
run_a_image: imgA().imageNumber,
run_a_filename: imgA().filename,
run_b_id: runB().id,
run_b_image: imgB().imageNumber,
run_b_filename: imgB().filename,
review_date: new Date().toISOString().split('T')[0],
system_version: "v0.4.3"
};
}
function updateBoundaryControls() {
const aAtStart = state.indexA <= 0;
const aAtEnd = state.indexA >= runA().images.length - 1;
const bAtStart = state.indexB <= 0;
const bAtEnd = state.indexB >= runB().images.length - 1;
$("aBack4").disabled = aAtStart; $("aBack1").disabled = aAtStart;
$("aForward1").disabled = aAtEnd; $("aForward4").disabled = aAtEnd;
$("bBack4").disabled = bAtStart; $("bBack1").disabled = bAtStart;
$("bForward1").disabled = bAtEnd; $("bForward4").disabled = bAtEnd;
$("runABoundary").textContent = aAtStart ? "Start of sequence reached." : aAtEnd ? "End of sequence reached." : "";
$("runBBoundary").textContent = bAtStart ? "Start of sequence reached." : bAtEnd ? "End of sequence reached." : "";
}
function updateAll(loadImage=true) {
if (!runA() || !runB()) return;
$("runAName").textContent = runA().shortName;
$("runBName").textContent = runB().shortName;
$("runAMeta").innerHTML = imageMeta(runA(), imgA());
$("runBMeta").innerHTML = imageMeta(runB(), imgB());
$("runCardA").classList.toggle("active-a", state.visible === "A");
$("runCardB").classList.toggle("active-b", state.visible === "B");
const cRun = currentRun(), cImg = currentImg();
if (cRun && cImg) {
$("currentViewing").textContent = Run ${state.visible} — ${cRun.shortName} — image ${cImg.imageNumber};
$("imageLabel").textContent = Run ${state.visible} | ${cImg.filename};
}
$("pairSummary").innerHTML =
Active Framework Viewport:A: ${escapeHtml(runA().shortName)} — #${imgA().imageNumber}
B: ${escapeHtml(runB().shortName)} — #${imgB().imageNumber};
if (loadImage) showCurrentImage();
updatePairRecords();
updateBoundaryControls();
preloadNearby();
}
function showCurrentImage() {
const im = $("mainImage");
if (!currentImg()) return;
$("loading").style.display = "flex";
im.style.display = "none";
state.panX = state.panY = 0;
im.onload = () => {
im.style.display = "block";
$("loading").style.display = "none";
fitImage();
setStatus(Active viewport loaded: ${currentImg().filename});
};
im.onerror = () => {
$("loading").textContent = "Cloudflare image request failed.";
setStatus(File unretrievable: ${currentImg().path}, true);
};
im.src = encodeURI(currentImg().path);
}
function fitImage() {
const wrap = $("viewerWrap"), im = $("mainImage");
if (!im.naturalWidth) return;
state.fitScale = Math.min((wrap.clientWidth-12)/im.naturalWidth, (wrap.clientHeight-12)/im.naturalHeight);
state.scale = state.fitScale;
state.panX = state.panY = 0;
applyTransform();
}
function actualSize() { state.scale = 1; state.panX = state.panY = 0; applyTransform(); }
function applyTransform() {
const im = $("mainImage");
im.style.width = ${im.naturalWidth}px;
im.style.height = ${im.naturalHeight}px;
im.style.transform = translate(-50%, -50%) translate(${state.panX}px, ${state.panY}px) scale(${state.scale});
$("zoomPct").textContent = ${Math.round(state.scale*100)}%;
}
function zoomBy(factor) {
state.scale = Math.max(state.fitScale0.5, Math.min(4, state.scalefactor));
applyTransform();
}
function setStatus(msg, warn=false) {
$("status").textContent = msg || "";
$("status").className = warn ? "status warn" : "status";
}
function navigate(which, delta) {
requestNavigation(() => {
const run = which === "A" ? runA() : runB();
const key = which === "A" ? "indexA" : "indexB";
state[key] = Math.max(0, Math.min(run.images.length-1, state[key] + delta));
updateAll(state.visible === which);
});
}
function jump(which) {
const input = which === "A" ? $("jumpA") : $("jumpB");
const run = which === "A" ? runA() : runB();
const idx = run.images.findIndex(i => i.imageNumber === Number(input.value.trim()));
if (idx < 0) { setStatus(Frame #${input.value} not present in this segment., true); return; }
requestNavigation(() => {
if (which === "A") state.indexA = idx; else state.indexB = idx;
updateAll(state.visible === which);
});
}
function requestNavigation(action) {
if (state.mode === "explore" || pairIsDocumented()) { action(); return; }
state.pendingAction = action;
showDocumentationModal();
}
function pairIsDocumented() { return state.observations.some(o => o.pair_key === pairKey()); }
function quickRecord(disposition, reason="") {
const record = baseRecord();
Object.assign(record, { disposition, notes: reason, feature_type: "None / Clear", extent_severity: "Unable to assess", confidence: "Not applicable" });
state.observations.push(record);
saveLocal(); updatePairRecords();
}
function showDocumentationModal() {
$("modalBackdrop").style.display = "flex";
$("modalReasonWrap").style.display = "none";
$("modalReason").value = "";
$("modalTitle").textContent = state.mode === "required" ? "Reviewer recording required" : "Minimal recording required";
$("modalText").innerHTML = Verification required prior to shifting focus:
Run A #${imgA().imageNumber} & Run B #${imgB().imageNumber};
const actions = [
["No notable finding", () => completeQuick("No notable finding")],
["Unable to assess", () => completeQuick("Unable to assess")],
["Return to layout", closeModal]
];
if (state.mode === "required") actions.splice(2, 0, ["Override validation", requestOverride]);
$("modalActions").innerHTML = "";
actions.forEach(([label, fn]) => {
const b = document.createElement("button"); b.textContent = label;
if (label === "Return to layout") b.className = "primary";
b.onclick = fn; $("modalActions").appendChild(b);
});
}
function completeQuick(disposition) { quickRecord(disposition); closeModal(); runPending(); }
function closeModal() { $("modalBackdrop").style.display = "none"; }
function runPending() { const a = state.pendingAction; state.pendingAction = null; if(a) a(); }
function requestOverride() {
$("modalReasonWrap").style.display = "block";
$("modalActions").innerHTML = "";
const save = document.createElement("button"); save.className="primary"; save.textContent="Apply Override";
save.onclick = () => {
if (!$("modalReason").value.trim()) { alert("Override entry requires justification."); return; }
quickRecord("Overridden", $("modalReason").value.trim()); closeModal(); runPending();
};
const cancel = document.createElement("button"); cancel.textContent = "Cancel"; cancel.onclick = showDocumentationModal;
$("modalActions").appendChild(save); $("modalActions").appendChild(cancel);
}
$("runSelectA").onchange = function() { state.activeRunKeyA = this.value; state.indexA = 0; updateAll(state.visible === "A"); };
$("runSelectB").onchange = function() { state.activeRunKeyB = this.value; state.indexB = 0; updateAll(state.visible === "B"); };
$("modeSelect").onchange = function() {
state.mode = this.value;
const isExplore = state.mode === "explore";
$("modeBanner").textContent = isExplore ? "Exploration — No Data Collection. Observations are disabled." : "Data Management System Active.";
$("modeBanner").className = mode-banner mode-${state.mode};
$("observationBlock").classList.toggle("disabled-panel", isExplore);
Array.from($("observationBlock").querySelectorAll("select, textarea, button")).forEach(el => el.disabled = isExplore);
updatePairRecords();
};
$("aBack4").onclick = () => navigate("A", -4); $("aBack1").onclick = () => navigate("A", -1);
$("aForward1").onclick = () => navigate("A", 1); $("aForward4").onclick = () => navigate("A", 4);
$("bBack4").onclick = () => navigate("B", -4); $("bBack1").onclick = () => navigate("B", -1);
$("bForward1").onclick = () => navigate("B", 1); $("bForward4").onclick = () => navigate("B", 4);
$("jumpABtn").onclick = () => jump("A"); $("jumpBBtn").onclick = () => jump("B");
$("viewABtn").onclick = function() { state.visible = "A"; this.className="primary"; $("viewBBtn").className=""; updateAll(true); };
$("viewBBtn").onclick = function() { state.visible = "B"; this.className="primary"; $("viewABtn").className=""; updateAll(true); };
$("zoomInBtn").onclick = () => zoomBy(1.2); $("zoomOutBtn").onclick = () => zoomBy(1/1.2);
$("zoomFitBtn").onclick = fitImage; $("zoomActualBtn").onclick = actualSize; $("zoomResetBtn").onclick = fitImage;
const viewerWrapElement = $("viewerWrap");
viewerWrapElement.onmousedown = (e) => { if(e.button!==0) return; state.isDragging=true; viewerWrapElement.classList.add("dragging"); state.startX=e.clientX-state.panX; state.startY=e.clientY-state.panY; };
window.onmousemove = (e) => { if(!state.isDragging) return; state.panX=e.clientX-state.startX; state.panY=e.clientY-state.startY; applyTransform(); };
window.onmouseup = () => { state.isDragging=false; viewerWrapElement.classList.remove("dragging"); };
viewerWrapElement.onwheel = (e) => { e.preventDefault(); zoomBy(e.deltaY < 0 ? 1.1 : 1/1.1); };
function saveLocal() { localStorage.setItem("tla_obs_v043", JSON.stringify(state.observations)); }
function loadLocal() { const d = localStorage.getItem("tla_obs_v043"); if(d) state.observations = JSON.parse(d); }
$("saveObsBtn").onclick = () => {
const r = baseRecord();
Object.assign(r, { disposition: "Observation logged", feature_type: $("featureType").value, extent_severity: $("severity").value, confidence: $("confidence").value, notes: $("obsNotes").value.trim() });
const idx = state.observations.findIndex(o => o.pair_key === r.pair_key);
if(idx >= 0) state.observations[idx] = r; else state.observations.push(r);
saveLocal(); updatePairRecords(); setStatus("Record saved locally.");
};
$("clearFormBtn").onclick = () => { $("featureType").selectedIndex = 0; $("severity").selectedIndex = 0; $("confidence").selectedIndex = 0; $("obsNotes").value = ""; };
function updatePairRecords() {
loadLocal();
$("recordCount").textContent = state.observations.length;
const currentRecord = state.observations.find(o => o.pair_key === pairKey());
const container = $("savedItemsContainer"); container.innerHTML = "";
if (currentRecord) {
const item = document.createElement("div"); item.className = "notice success";
item.innerHTML =
Logged Data: ${escapeHtml(currentRecord.disposition)}
${escapeHtml(currentRecord.feature_type)} | ${escapeHtml(currentRecord.notes || "No notes entered")};
container.appendChild(item);
} else if (state.mode !== "explore") {
const item = document.createElement("div"); item.className = "notice warning";
item.innerHTML =
Awaiting Entry: No active records logged for this dynamic frame combination.;
container.appendChild(item);
}
}
function preloadNearby() {
const run = currentRun();
if (!run) return;
const idx = state.visible === "A" ? state.indexA : state.indexB;
[-1, 1].forEach(offset => {
const target = run.images[idx + offset];
if (target) { const i = new Image(); i.src = encodeURI(target.path); }
});
}
$("exportCsvBtn").onclick = () => {
if(!state.observations.length) return;
const headers = Object.keys(state.observations[0]);
let csv = headers.join(",") + "\n";
state.observations.forEach(o => { csv += headers.map(k => "${String(o[k]).replace(/"/g, '""')}").join(",") + "\n"; });
const blob = new Blob([csv], {type:"text/csv"});
const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "tla_data_export.csv"; a.click();
};
$("exportJsonBtn").onclick = () => {
if(!state.observations.length) return;
const blob = new Blob([JSON.stringify(state.observations, null, 2)], {type:"application/json"});
const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "tla_data_export.json"; a.click();
};
$("clearDataBtn").onclick = () => { if(confirm("Purge local simulation records database?")) { state.observations = []; saveLocal(); updatePairRecords(); } };
window.onload = () => {
const sA = $("runSelectA"), sB = $("runSelectB");
sA.innerHTML = sB.innerHTML = Object.keys(DATA_DICTIONARY).map(k =>
).join("");
sA.value = state.activeRunKeyA;
sB.value = state.activeRunKeyB;
loadLocal();
updateAll(true);
};