๐Ÿˆ Football Play Maker
Tap target playerโ€ฆ
'; w.document.write(html); w.document.close(); setTimeout(function(){w.focus();w.print();},400); } function downloadPlayPNG(){ var clone=svg.cloneNode(true); clone.setAttribute('viewBox','0 0 '+FW+' '+FH); clone.setAttribute('width',FW); clone.setAttribute('height',FH); var xml=new XMLSerializer().serializeToString(clone); var svg64=btoa(unescape(encodeURIComponent(xml))); var img=new Image(); img.onload=function(){ var canvas=document.createElement('canvas'); canvas.width=FW*2; canvas.height=FH*2; var ctx=canvas.getContext('2d'); ctx.fillStyle='#123b26'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0,canvas.width,canvas.height); var a=document.createElement('a'); a.href=canvas.toDataURL('image/png'); a.download=(play.name||'play').replace(/[^a-z0-9]+/gi,'_')+'.png'; a.click(); }; img.src='data:image/svg+xml;base64,'+svg64; } /* ================= MOBILE SHEETS ================= */ var leftPanel=document.getElementById('leftPanel'), rightPanel=document.getElementById('rightPanel'), backdrop=document.getElementById('sheetBackdrop'); function openSheet(which){ closeSheets(); if(which==='left'){leftPanel.classList.add('open');} else {rightPanel.classList.add('open');} backdrop.classList.add('show'); } function closeSheets(){leftPanel.classList.remove('open');rightPanel.classList.remove('open');backdrop.classList.remove('show');} backdrop.addEventListener('click',closeSheets); document.getElementById('setupBtn').onclick=function(){openSheet('left');}; document.getElementById('editBtn').onclick=function(){openSheet('right');}; /* ================= MORE MENU ================= */ document.getElementById('moreBtn').addEventListener('click',function(e){ var old=document.getElementById('moreMenu'); if(old){old.remove();return;} var m=document.createElement('div'); m.className='dropdown-menu'; m.id='moreMenu'; var rect=e.target.getBoundingClientRect(); m.style.right='10px'; m.style.top=(rect.bottom+6)+'px'; m.style.left='auto'; var items=[ ['๏ผ‹ New Blank Play',function(){play=blankPlay();selectedPlayerId=null;fullRender();}], ['โง‰ Duplicate Play',duplicateCurrentPlay], ['โ‡„ Mirror Play',mirrorCurrentPlay], ['๐Ÿ–จ Print This Play',printCurrentPlay], ['โฌ‡ Download Play Image',downloadPlayPNG], ['๐Ÿ“š Go to My Playbooks',function(){window.location.href='/playbook';}] ]; items.forEach(function(it){var b=document.createElement('button');b.textContent=it[0];b.onclick=function(){it[1]();m.remove();};m.appendChild(b);}); document.body.appendChild(m); setTimeout(function(){document.addEventListener('pointerdown',function h(ev){if(!m.contains(ev.target)&&ev.target.id!=='moreBtn'){m.remove();document.removeEventListener('pointerdown',h);}});},10); }); /* ================= BOTTOM BAR & TOP TOOLBAR BINDINGS ================= */ document.getElementById('undoBtn').onclick=undo; document.getElementById('redoBtn').onclick=redo; document.getElementById('clearBtn').onclick=function(){ if(confirm('Clear all players, routes and ball path from this play?')){pushHistory();play.players=[];play.assignments=[];play.ballPath=[];selectedPlayerId=null;fullRender();scheduleAutosave();} }; document.getElementById('playBtn').onclick=toggleAnimPlayPause; document.getElementById('resetAnimBtn').onclick=resetAnim; document.getElementById('saveBtn').onclick=saveCurrentPlay; document.getElementById('zoomIn').onclick=function(){applyZoom(0.85);}; document.getElementById('zoomOut').onclick=function(){applyZoom(1.18);}; document.getElementById('zoomFit').onclick=fitField; document.getElementById('undoPointBtn').onclick=undoDrawPoint; document.getElementById('finishDrawBtn').onclick=finishDrawing; document.getElementById('cancelDrawBtn').onclick=cancelDrawing; document.getElementById('cancelBallPickBtn').onclick=cancelBallPick; /* ================= AUTOSAVE / RESTORE ================= */ var autosaveTimer=null; function scheduleAutosave(){ clearTimeout(autosaveTimer); autosaveTimer=setTimeout(function(){ safeSet(DRAFT_KEY,JSON.stringify({play:play,activePlaybookId:APP.activePlaybookId,ts:Date.now()})); },800); } function clearAutosaveDraft(){try{localStorage.removeItem(DRAFT_KEY);}catch(e){}} function checkForDraftRestore(){ try{ var raw=safeGet(DRAFT_KEY); if(!raw)return false; var d=JSON.parse(raw); if(!d||!d.play)return false; if(Date.now()-d.ts>1000*60*60*24*7){clearAutosaveDraft();return false;} var root=document.getElementById('modalRoot'); root.innerHTML=''; var closeD=function(){ try{if(root)root.innerHTML='';}catch(e){} if(!SESSION_TUTORIAL_SEEN && APP.settings && !APP.settings.seenTutorial)showOnboarding(); }; document.getElementById('discardDraftBtn').addEventListener('click',function(e){e.preventDefault();try{clearAutosaveDraft();}catch(e2){}closeD();}); document.getElementById('restoreDraftBtn').addEventListener('click',function(e){e.preventDefault();try{play=d.play;if(d.activePlaybookId)APP.activePlaybookId=d.activePlaybookId;fullRender();}catch(e3){}closeD();}); return true; }catch(e){return false;} } /* ================= EDIT HANDSHAKE FROM /playbook ================= */ function checkEditRequest(){ var raw=safeGet('football_playmaker_edit_request'); if(!raw)return false; try{ var req=JSON.parse(raw); localStorage.removeItem('football_playmaker_edit_request'); var pb=APP.playbooks.filter(function(p){return p.id===req.playbookId;})[0]; if(!pb)return false; if(req.playId){ var pl=pb.plays.filter(function(p){return p.id===req.playId;})[0]; if(pl){play=deepClone(pl);APP.activePlaybookId=pb.id;return true;} } else { APP.activePlaybookId=pb.id; if(req.newFromFormation){play=blankPlay();applyFormationToPlay(play,req.newFromFormation,APP.customFormations);return true;} play=blankPlay(); return true; } }catch(e){} return false; } /* ================= ONBOARDING ================= */ var SESSION_TUTORIAL_SEEN=false; function closeOnboarding(root){ // Guaranteed to clear the modal no matter what else fails. try{ if(!APP.settings)APP.settings={}; APP.settings.seenTutorial=true; }catch(e){} try{ saveAppData(APP); }catch(e){} SESSION_TUTORIAL_SEEN=true; try{ if(!root)root=document.getElementById('modalRoot'); if(root)root.innerHTML=''; }catch(e){} } function showOnboarding(){ var root=document.getElementById('modalRoot'); root.innerHTML=''; var go=function(e){ if(e){e.preventDefault();e.stopPropagation();} closeOnboarding(root); }; document.getElementById('skipTutBtn').addEventListener('click',go); document.getElementById('skipTutBtn2').addEventListener('click',go); document.getElementById('closeTutX').addEventListener('click',go); var mb=document.getElementById('mbO'); if(mb)mb.addEventListener('pointerdown',function(e){ if(e.target.id==='mbO')go(); }); } /* ================= INIT ================= */ function init(){ buildLeftPanel(); var restored=checkEditRequest(); if(!restored){ // start with a demo-friendly blank I-formation play applyFormationToPlay(play,'I Formation',APP.customFormations); play.name='Untitled Play'; } fitField(); fullRender(); setActiveTab('player'); var hadDraftPrompt=false; if(!restored)hadDraftPrompt=checkForDraftRestore(); if(!hadDraftPrompt && !SESSION_TUTORIAL_SEEN && APP.settings && !APP.settings.seenTutorial)showOnboarding(); window.addEventListener('resize',function(){fitField();fullRender();}); window.addEventListener('beforeunload',function(){ if(play.players.length){safeSet(DRAFT_KEY,JSON.stringify({play:play,activePlaybookId:APP.activePlaybookId,ts:Date.now()}));} }); } try{ init(); }catch(e){ showStorageError(); console.error(e); }