Basketball Shot Map

'; const w = window.open('', '_blank'); if(!w){ toast('Popup blocked — allow popups to export Summary PDF.'); modal.confirm({title:'Popup Blocked', sub:'Allow popups for this site to use Summary PDF.', okText:'OK', cancelText:'Close'}); return; } w.document.open(); w.document.write(html); w.document.close(); w.focus(); setTimeout(()=>w.print(), 250); } // ---- Events ---- el.gameSelect.addEventListener('change', (e)=>{ state.currentGameId = e.target.value; state.viewingPeriod=null; closePicker(); renderAll(); }); el.playerFilter.addEventListener('change', (e)=>{ state.playerFilter = e.target.value; renderAll(); }); el.newGame.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); newGame(); }); el.deleteGame.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); deleteGame(); }); el.reset.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); resetGame(); }); el.share.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); share(); }); el.export.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); exportPDF(); }); el.summaryPdf.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); exportSummaryPdf(); }); el.undo.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); undo(); }); el.addPlayer.addEventListener('click', (e)=>{ e.preventDefault(); e.stopPropagation(); addPlayer(); }); // Roster delegated actions el.roster.addEventListener('click', (e)=>{ const b = e.target.closest('button'); if(!b) return; const act = b.dataset.act; const id = b.dataset.id; if(act === 'edit' && id) editPlayer(id); if(act === 'delete' && id) deletePlayer(id); if(act === 'clearUnassigned') clearUnassignedShots(); }); el.pickerClose.addEventListener('click', ()=>{ closePicker(); renderAll(); }); el.pickerBackdrop.addEventListener('click', ()=>{ closePicker(); renderAll(); }); // Period nav (delegated) el.periodNav.addEventListener('click', async (e)=>{ const b = e.target.closest('.periodNavBtn'); if(!b) return; const g = curGame(); if(b.dataset.period){ const p = parseInt(b.dataset.period,10); if(p === g.currentPeriod && state.viewingPeriod===null) return; state.viewingPeriod = p; closePicker(); renderAll(); return; } if(b.dataset.action==='total'){ state.viewingPeriod='total'; closePicker(); renderAll(); return; } if(b.dataset.action==='resume'){ state.viewingPeriod=null; closePicker(); renderAll(); return; } if(b.dataset.action==='next'){ await nextPeriod(); return; } if(b.dataset.action==='final'){ await finalScore(); return; } }); // Picker select (delegated) el.pickerGrid.addEventListener('click', (e)=>{ const b = e.target.closest('.pickBtn'); if(!b) return; const pid = b.dataset.playerId; if(pid) commitShot(pid); }); // Tap shot markers to edit el.court.addEventListener('click', (e)=>{ const target = e.target; if(target && target.tagName && target.tagName.toLowerCase()==='circle' && target.dataset && target.dataset.shotId){ e.preventDefault(); e.stopPropagation(); closePicker(); openShotEditor(target.dataset.shotId); } }, true); // Tap/double-tap on court (record shots) function beginTap(evt){ // If tapped on a marker, ignore (handled above) if(evt.target && evt.target.tagName && evt.target.tagName.toLowerCase()==='circle' && evt.target.dataset && evt.target.dataset.shotId){ return; } if(state.viewingPeriod !== null){ toast('Resume to record'); return; } const now = Date.now(); const p = svgPoint(evt.clientX, evt.clientY); const pos = svgToWrapPx(p.x,p.y); beginTap._lastTime = beginTap._lastTime || 0; beginTap._lastPos = beginTap._lastPos || null; const isDouble = beginTap._lastTime && (now - beginTap._lastTime) < DOUBLE_TAP_MS && beginTap._lastPos && Math.hypot(beginTap._lastPos.x - pos.x, beginTap._lastPos.y - pos.y) < 22; if(beginTap._timer){ clearTimeout(beginTap._timer); beginTap._timer = null; } if(isDouble){ state.pendingShot = { x:p.x, y:p.y, made:true }; openPickerAt(pos.x,pos.y,true); beginTap._lastTime = 0; beginTap._lastPos = null; return; } state.pendingShot = { x:p.x, y:p.y, made:false }; beginTap._lastTime = now; beginTap._lastPos = pos; beginTap._timer = setTimeout(()=>{ beginTap._timer=null; if(state.pendingShot && state.pendingShot.made===false){ openPickerAt(pos.x,pos.y,false); } }, DOUBLE_TAP_MS + 30); } el.court.addEventListener('pointerdown', (e)=>{ if(el.court.setPointerCapture) el.court.setPointerCapture(e.pointerId); beginTap(e); }); // init loadFromHash(); load(); ensurePlayerIds(); if(!state.currentGameId) state.currentGameId = state.games[0].id; renderAll(); } if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', ()=>setTimeout(init, 50)); } else { setTimeout(init, 50); } })();