Copy of Lacrosse Shot Map

🥍 Lacrosse Shot Map PRO

Game
Goalie

ROSTER

1️⃣ FIELD POSITION (Half Field)

Goal
Saved
Pipe
Miss
Marker appears immediately
Goals
0
Shots
0
SOG%
0%
Shot%
0%

2️⃣ SHOT LOCATION

Tap inside goal (Goal/Saved), orange pipes (Pipe), outside (Miss)

3️⃣ SHOT DETAILS

Player
Hand
Left
Right
Release
Overhand
Sidearm
Underhand
Special
Quickstick
Goose
BTB

Zone & Attribute Stats

G = Goals • Att = Attempts (shots on goal). Stick-side based on shot placement.

Selected Player Mini Heat Grid

Select "All" (view-only) or a player. Shows only shots on goal (goals + saves).

Stick Side vs Off-Stick

Hand / Release / Special / Bounce

🥍

Welcome to Shot Map PRO

1
Set Up Your Roster

After this intro, you'll add your players. You can also use Bulk Add later.

2
Tap the Field

Mark where each shot was taken from on the half-field view.

3
Tap Shot Location

Tap inside the goal (Goal/Saved), on the pipes (Pipe), or outside (Miss).

4
Add Details & Save

Select player, hand, release, and any special attributes — then save.

💡 Pro Tips
  • Live Mode: Use 🎯 Live Mode for quick mobile entry during games
  • Season View: Select "Season (All Games)" to see aggregated stats
  • PDF Reports: Export summaries for games, season, or individual players
  • Undo: Made a mistake? Use Undo to step back
👥

Set Up Your Roster

Enter player names (one per line) or use commas to separate:

Tip: You can also add/edit players later from the Roster panel.
1️⃣ Field Position
Tap where the shot was taken
'; var w = window.open('', '_blank'); if(!w){ toast('Popup blocked — allow popups to export PDF.'); return; } w.document.open(); w.document.write(html); w.document.close(); w.focus(); setTimeout(function(){ w.print(); }, 250); } function addPlayer(){ var name = prompt('Player name:'); if(!name) return; state.players.push({id: uid(), name: name.trim()}); toast('Player added'); renderAll(); } function renamePlayer(id){ var p = state.players.find(function(x){ return x.id===id; }); if(!p) return; var name = prompt('Rename player:', p.name); if(!name) return; p.name = name.trim(); toast('Player updated'); renderAll(); } function removePlayer(id){ var p = state.players.find(function(x){ return x.id===id; }); if(!p) return; if(!confirm('Remove '+p.name+'? Shots will remain but may show unassigned.')) return; state.players = state.players.filter(function(x){ return x.id !== id; }); state.games.forEach(function(gm){ gm.shots.forEach(function(s){ if(s.playerId === id) s.playerId = null; }); }); toast('Player removed'); renderAll(); } function resetRoster(){ if(!confirm('Reset roster to Player 1-5?')) return; state.players = []; for(var n=1;n<=5;n++) state.players.push({id: uid(), name:'Player '+n}); toast('Roster reset'); renderAll(); } el.newGame.addEventListener('click', newGame); el.export.addEventListener('click', exportData); el.summaryPdf.addEventListener('click', exportSummaryPdf); el.undo.addEventListener('click', undo); el.loadDemo.addEventListener('click', toggleDemoMode); // Tutorial functions var TUTORIAL_KEY = isMember ? ('lax_tutorial_seen_'+customerId) : 'lax_tutorial_seen_guest'; var ROSTER_SETUP_KEY = isMember ? ('lax_roster_setup_'+customerId) : 'lax_roster_setup_guest'; function showTutorial(){ el.tutorial.classList.add('show'); el.tutorialBackdrop.classList.add('show'); } function hideTutorial(){ el.tutorial.classList.remove('show'); el.tutorialBackdrop.classList.remove('show'); if(el.tutorialDontShow.checked){ try{ storage.setItem(TUTORIAL_KEY, '1'); }catch(e){} } // Show roster setup after tutorial if roster is default if(shouldShowRosterSetup()){ setTimeout(function(){ showRosterSetup(true); }, 300); } } function shouldShowTutorial(){ try{ return !storage.getItem(TUTORIAL_KEY); }catch(e){ return true; } } function shouldShowRosterSetup(){ try{ if(storage.getItem(ROSTER_SETUP_KEY)) return false; // Check if roster is still default (Player 1, Player 2, etc) var isDefault = state.players.length === 5 && state.players.every(function(p, i){ return p.name === 'Player '+(i+1); }); return isDefault; }catch(e){ return false; } } function showRosterSetup(forceShow){ if(!forceShow && !shouldShowRosterSetup()) return; el.rosterSetup.classList.add('show'); el.rosterSetupBackdrop.classList.add('show'); el.bulkRoster.value = ''; el.rosterDontShow.checked = false; el.bulkRoster.focus(); } function hideRosterSetup(){ el.rosterSetup.classList.remove('show'); el.rosterSetupBackdrop.classList.remove('show'); // Only permanently dismiss if checkbox is checked if(el.rosterDontShow.checked){ try{ storage.setItem(ROSTER_SETUP_KEY, '1'); }catch(e){} } } function saveBulkRoster(){ var text = el.bulkRoster.value.trim(); if(!text){ toast('Enter at least one player name'); return; } // Parse names - split by newlines or commas var names = text.split(/[\n,]+/) .map(function(n){ return n.trim(); }) .filter(function(n){ return n.length > 0; }); if(names.length === 0){ toast('Enter at least one player name'); return; } // Check if current roster is default var isDefault = state.players.length === 5 && state.players.every(function(p, i){ return p.name === 'Player '+(i+1); }); if(isDefault){ // Replace default roster with new names state.players = names.map(function(name, i){ return { id: 'p'+(i+1), name: name }; }); } else { // Add to existing roster var maxId = 0; state.players.forEach(function(p){ var num = parseInt(p.id.replace('p',''), 10); if(num > maxId) maxId = num; }); names.forEach(function(name){ maxId++; state.players.push({ id: 'p'+maxId, name: name }); }); } save(); renderRoster(); renderPlayerSelect(); hideRosterSetup(); toast('✓ Added ' + names.length + ' player' + (names.length > 1 ? 's' : '')); } el.help.addEventListener('click', showTutorial); el.tutorialClose.addEventListener('click', hideTutorial); el.tutorialBackdrop.addEventListener('click', hideTutorial); el.rosterSkip.addEventListener('click', hideRosterSetup); el.rosterSave.addEventListener('click', saveBulkRoster); el.rosterSetupBackdrop.addEventListener('click', hideRosterSetup); // Live Mode event listeners el.liveMode.addEventListener('click', toggleLiveMode); el.liveClose.addEventListener('click', hideLiveModal); el.liveBackdrop.addEventListener('click', hideLiveModal); // Show tutorial on first visit, or roster setup if tutorial was already seen if(shouldShowTutorial()){ setTimeout(showTutorial, 500); } else if(shouldShowRosterSetup()){ // Tutorial was already seen but roster is still default setTimeout(function(){ showRosterSetup(true); }, 500); } el.addPlayer.addEventListener('click', addPlayer); el.bulkAdd.addEventListener('click', function(){ showRosterSetup(true); }); el.resetRoster.addEventListener('click', resetRoster); el.gameSelect.addEventListener('change', function(){ state.currentGameId = el.gameSelect.value; state.pendingShot = null; state.step = 'field'; closePicker(); if(isSeasonView()){ toast('📊 Season View – All games'); } renderAll(); }); el.goalieHand.addEventListener('change', function(){ state.goalieHand = el.goalieHand.value; toast('Goalie: ' + (state.goalieHand==='right' ? 'Right-handed' : 'Left-handed')); renderAll(); }); el.playerSelect.addEventListener('change', function(){ // Re-render everything when player selection changes drawField(); drawGoal(); renderStats(); renderZoneStats(); renderPlayerMiniGrid(); updateSaveButtonState(); }); load(); renderAll(); } if(document.readyState === 'loading'){ document.addEventListener('DOMContentLoaded', function(){ setTimeout(initLaxShotMap, 60); }); } else { setTimeout(initLaxShotMap, 60); } document.addEventListener('shopify:section:load', function(){ setTimeout(initLaxShotMap, 60); }); })();