(() => {
 const $=s=>document.querySelector(s);
 const form=$('#detectForm'),file=$('#image'),zone=$('#dropzone'),notice=$('#notice'),run=$('#runBtn');
 let current=null,view='output',page=1,total=0,previewUrl=null;
 let modelCatalog=[];
 const modal=$('#modelModal'),capability=$('#capabilityFilter'),cards=$('#modelCards');
 const renderModels=()=>{cards.innerHTML=modelCatalog.filter(m=>m.capability===capability.value).map(m=>'<button class="model-card" type="button" data-key="'+safe(m.id)+'"><strong>'+safe(m.name)+'</strong><span>'+safe(m.description)+'</span><small>'+safe(m.framework.toUpperCase())+'</small></button>').join('')||'<p>No enabled models available.</p>';cards.querySelectorAll('button').forEach(b=>b.addEventListener('click',()=>{const m=modelCatalog.find(x=>x.id===b.dataset.key);$('#model').value=m.id;$('#selectedCapability').textContent=(m.capability==='road'?'Road & Pothole Detection':'Animal Detection')+' · '+m.description;modal.hidden=true;$('#chooseModel').focus();}));};
 const loadModels=async()=>{try{modelCatalog=await json('/?handler=Models');$('#model').innerHTML=modelCatalog.map(m=>'<option value="'+safe(m.id)+'">'+safe(m.name)+'</option>').join('');renderModels();}catch(e){message('Unable to load model registry: '+e.message);}};
 $('#chooseModel').addEventListener('click',()=>{const m=modelCatalog.find(x=>x.id===$('#model').value);if(m)capability.value=m.capability;renderModels();modal.hidden=false;$('#closeModels').focus();});
 $('#closeModels').addEventListener('click',()=>{modal.hidden=true;$('#chooseModel').focus();});
 modal.addEventListener('click',e=>{if(e.target===modal)modal.hidden=true;});
 document.addEventListener('keydown',e=>{if(e.key==='Escape'&&!modal.hidden){modal.hidden=true;$('#chooseModel').focus();}});
 capability.addEventListener('change',renderModels);
 const token=()=>form.querySelector('input[name="__RequestVerificationToken"]').value;
 const safe=(text)=>{const e=document.createElement('span');e.textContent=String(text);return e.innerHTML};
 const message=(text,kind='error')=>{notice.textContent=text;notice.className=kind};
 const assignFile=f=>{if(!f)return;const dt=new DataTransfer();dt.items.add(f);file.files=dt.files;$('#filename').hidden=false;$('#filename').textContent='Selected: '+f.name;
 if(previewUrl)URL.revokeObjectURL(previewUrl);previewUrl=URL.createObjectURL(f);current=null;view='output';
 document.querySelectorAll('.switch').forEach(x=>x.classList.toggle('active',x.dataset.view==='output'));
 $('#resultImage').src=previewUrl;$('#resultImage').hidden=false;$('#emptyPreview').hidden=true;
 $('#resultTag').textContent='IMAGE READY';$('#metricCount').textContent='—';$('#metricTime').textContent='—';$('#metricModel').textContent='—';$('#detectionList').textContent='Run detection to show objects.';message('Image ready. Click Run Detection.','success');
};
 file.addEventListener('change',()=>{if(file.files[0])assignFile(file.files[0])});
 ['dragenter','dragover'].forEach(type=>zone.addEventListener(type,e=>{e.preventDefault();zone.classList.add('drag')}));
 ['dragleave','drop'].forEach(type=>zone.addEventListener(type,e=>{e.preventDefault();zone.classList.remove('drag')}));
 zone.addEventListener('drop',e=>assignFile([...e.dataTransfer.files][0]));
 $('#confidence').addEventListener('input',e=>$('#confOutput').textContent=Math.round(Number(e.target.value)*100)+'%');
 const show=(item)=>{current=item;$('#resultImage').hidden=false;$('#emptyPreview').hidden=true;$('#resultImage').src=view==='output'?item.outputUrl:item.inputUrl;$('#resultTag').textContent='DETECTION COMPLETE';$('#metricCount').textContent=item.count;$('#metricTime').textContent=item.elapsedMs+' ms';$('#metricModel').textContent=item.model||$('#model').value;const rows=item.detections||[];$('#detectionList').innerHTML=rows.length?rows.map(d=>'<div class="object-row"><b>'+safe(d.animal)+'</b><span>'+Math.round(d.confidence*100)+'% confidence</span></div>').join(''):'<div class="noobjects">No supported animal categories detected.</div>';};
 document.querySelectorAll('.switch').forEach(btn=>btn.addEventListener('click',()=>{document.querySelectorAll('.switch').forEach(x=>x.classList.toggle('active',x===btn));view=btn.dataset.view;if(current)$('#resultImage').src=view==='output'?current.outputUrl:current.inputUrl;else if(previewUrl)$('#resultImage').src=previewUrl;}));
 document.querySelectorAll('.tab').forEach(btn=>btn.addEventListener('click',()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('active',x===btn));const chosen=btn.dataset.tab;$('#imageTab').hidden=chosen!=='image';$('#placeholderTab').hidden=chosen==='image';$('#placeholderTitle').textContent=chosen==='video'?'Video Detection':'Live Streaming Detection';}));
 async function json(url,opts){const response=await fetch(url,opts);let body;try{body=await response.json()}catch{if(response.status===429)throw Error('Too many requests. Please retry shortly.');throw Error('Server returned HTTP '+response.status+'. Please retry.');}if(!response.ok)throw Error(body.error||'Request failed ('+response.status+')');return body;}
 form.addEventListener('submit',async e=>{e.preventDefault();if(!file.files.length)return message('Select an image first.');run.disabled=true;run.textContent='Processing image…';message('Analyzing image. Initial model loading may take longer.','success');try{const data=new FormData(form);const result=await json('/?handler=Detect',{method:'POST',body:data,headers:{'RequestVerificationToken':token()}});result.model=$('#model').value;show(result);message('Detection completed successfully.','success');await Promise.all([loadLogs(result.id),loadHistory()]);}catch(err){message(err.message);await loadLogs()}finally{run.disabled=false;run.innerHTML='<span>⟡</span> Run Detection <span class="arrow">→</span>';}});
 async function loadLogs(id){try{const logs=await json('/?handler=Logs'+(id?'&id='+encodeURIComponent(id):''));$('#logs').innerHTML=logs.length?logs.map(x=>'<div class="logline"><time>'+safe(new Date(x.time).toLocaleTimeString())+'</time><span class="level '+(x.level==='ERROR'?'error':'')+'">'+safe(x.level)+'</span><span>'+safe(x.message)+'</span></div>').join(''):'<div class="log-placeholder">No log entries yet.</div>';}catch(err){$('#logs').textContent=err.message;}}
 async function loadHistory(){try{const result=await json('/?handler=History&page='+page+'&pageSize=10');total=result.total;$('#pageInfo').textContent='Page '+page+' / '+Math.max(1,Math.ceil(total/10));$('#prevPage').disabled=page<=1;$('#nextPage').disabled=page*10>=total;$('#history').innerHTML=result.items.length?result.items.map(x=>'<div class="history-item"><img class="thumb" src="'+x.outputUrl+'" alt=""><div class="history-meta"><strong title="'+safe(x.filename)+'">'+safe(x.filename)+'</strong><span>'+safe(x.model)+' · '+x.count+' objects · '+safe(new Date(x.createdAt).toLocaleString())+'</span></div><button class="secondary view" data-id="'+x.id+'">View</button><button class="secondary delete" data-id="'+x.id+'" aria-label="Delete history item">Delete</button></div>').join(''):'<div class="noobjects">No detection history yet.</div>';$('#history').querySelectorAll('.view').forEach(b=>b.addEventListener('click',()=>{const item=result.items.find(x=>x.id===b.dataset.id);show(item);loadLogs(item.id);window.scrollTo({top:0,behavior:'smooth'});}));$('#history').querySelectorAll('.delete').forEach(b=>b.addEventListener('click',async()=>{if(!confirm('Delete this record and its uploaded/annotated images?'))return;try{await json('/?handler=Delete',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','RequestVerificationToken':token()},body:'id='+encodeURIComponent(b.dataset.id)});if(current?.id===b.dataset.id){current=null;$('#resultImage').hidden=true;$('#emptyPreview').hidden=false;$('#metricCount').textContent='—';$('#metricTime').textContent='—';$('#metricModel').textContent='—';$('#detectionList').textContent='No detections yet.';$('#resultTag').textContent='AWAITING INPUT';}await loadHistory();await loadLogs();}catch(err){message(err.message);}}));}catch(err){$('#history').textContent=err.message;}}
 $('#prevPage').addEventListener('click',()=>{if(page>1){page--;loadHistory()}});$('#nextPage').addEventListener('click',()=>{if(page*10<total){page++;loadHistory()}});$('#refreshLogs').addEventListener('click',()=>loadLogs(current?.id));$('#refreshHistory').addEventListener('click',loadHistory);loadModels();loadHistory();loadLogs();
})();

.model-picker{display:flex;gap:8px;flex-wrap:wrap}.model-picker select{min-width:0;flex:1;background:#132029;color:#e7f4f0;border:1px solid #35484b;border-radius:6px;padding:10px}.model-caption{color:#93aca9;font-size:10px;line-height:1.5}.model-modal{position:fixed;inset:0;background:#000b;z-index:1000;display:flex;justify-content:center;align-items:center;padding:18px}.modal-card{background:#111c24;border:1px solid #3a5758;border-radius:14px;width:min(650px,100%);max-height:calc(100dvh - 36px);overflow:auto;padding:25px;box-shadow:0 24px 90px #0009}.modal-header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:20px}.modal-header h2{font-size:22px;margin:8px 0}.modal-card>label{display:block;color:#abc3bd;font-size:10px;letter-spacing:1px;margin:12px 0}.modal-card select{width:100%;background:#0e1820;color:#edf6f1;padding:13px;border:1px solid #3c5855;border-radius:7px}.model-cards{display:grid;gap:10px;margin-top:18px}.model-card{background:#172731;color:#e6f2ef;border:1px solid #34504f;border-radius:8px;text-align:left;padding:17px;display:flex;flex-direction:column;gap:5px}.model-card:hover,.model-card:focus-visible{border-color:#6ff4c0}.model-card span{color:#a8b7b8;font-size:11px}.model-card small{color:#6ff4c0;font-size:9px;letter-spacing:1px}
