# Copilot Instructions for Turnierplaner ## Project Overview **Turnierplaner** (Tournament Planner) is a German-language, client-side volleyball tournament management app for the "NVJ" organization. It handles team registration, round-robin rotation logic, live scoring, and leaderboards for two leagues: "Bundesliga" and "Champions League". ### Tech Stack - **Frontend**: Vanilla HTML, CSS, JavaScript (ES6+) - **Persistence**: LocalStorage for all data (no backend) - **UI Pattern**: Multi-page app with shared state via localStorage - **Build**: None - runs directly in browser via file:// or static server ## Architecture & Key Components ### Two-Page Structure 1. **[index.html](index.html)** + [script.js](script.js): Team entry interface - Add/remove teams with name, club, and motto fields - Set field count (used to calculate fields per league) - Export/import tournament configuration as JSON - Navigate to planning view with "Weiter →" 2. **[planning.html](planning.html)** + [planning.js](planning.js): Live tournament management - Display current round matchups on fields (grid layout: field number | team 1 | team 2) - Show waiting teams below the playing fields - Live score input per match with automatic point calculation - Round rotation system with "Nächste Runde" button - Timer with custom input (MM:SS or seconds) - Modals for: Points History, Scoreboard (rankings), Reset confirmation ### Data Flow & State Management **Three localStorage Keys** ([planning.js](planning.js#L1-L3)): - `turnierplaner_data`: Teams + field count (shared between pages) - `turnierplaner_rotation`: Current round number + team order per league - `turnierplaner_scores`: Match results keyed by `"league:fieldNum"` **Critical Logic**: Field allocation in [planning.js](planning.js#L63-L76) ```javascript // fieldCount = number of physical fields (from user input) // Each field has 2 halves, so max playing teams = fieldCount * 2 const maxPlayingTeams = fieldCount * 2; const playingTeamsCount = Math.min(teams.length, maxPlayingTeams); const fieldsPerLeague = Math.ceil(playingTeamsCount / 2); ``` **Round Rotation** ([planning.js](planning.js#L574-L611)): Three rotation strategies based on waiting team count: - **Fall 1** (0 waiting, teams === fieldCount * 2): Team at index 0 stays, team at index 1 moves to end - **Fall 2** (1 waiting, teams === fieldCount * 2 + 1): All rotate by 1, first team goes to end - **Fall 3** (2+ waiting, teams > fieldCount * 2 + 1): Swap playing/waiting blocks entirely **Point Calculation** ([planning.js](planning.js#L544-L565)): Winner gets `|score1 - score2| + 2`, loser gets `-|score1 - score2|`, tie = 0 ## Development Workflows ### Running Locally ```bash # No build step - open directly in browser start index.html # Windows # Or use a local server for better CORS handling npx serve . ``` ### Testing Changes 1. Open browser DevTools → Application → Local Storage 2. Clear `turnierplaner_*` keys to reset state 3. Use Export/Import JSON to preserve test data ### Debugging Rotation Logic Add breakpoints in [planning.js](planning.js#L574) `rotateLeague()`. Check `rotationState.teamOrder` vs actual team indices. ## Code Conventions & Patterns ### Naming & Structure - German UI text (labels, buttons, alerts), English code (variables, functions) - Global functions (no modules): `onclick="functionName()"` in HTML - Modal pattern: `open{Name}Modal()` / `close{Name}Modal()` with `display: block/none` - Auto-save: Input event listeners trigger `saveData()` ([script.js](script.js#L94-L99)) ### Score Input Pattern ([planning.js](planning.js#L116-L120)) ```javascript // Inline onchange in field card HTML onchange="updateMatchScore('bundesliga', 1, this.value, otherInput.value)" ``` ### XSS Prevention Use `escapeHtml()` ([script.js](script.js#L203-L210)) for user input in team names/clubs/mottos ### Mobile-First CSS ([styles.css](styles.css)) - `viewport-fit=cover` for iOS safe areas - Touch-optimized: `min-height: 44px`, `touch-action: manipulation` - Responsive grid: `grid-template-columns: 1fr 1fr` → `1fr` on mobile ## Integration Points & Dependencies ### External Dependencies None - pure vanilla JavaScript ### Browser APIs - **LocalStorage**: All persistence ([script.js](script.js#L1), [planning.js](planning.js#L1-L3)) - **File API**: JSON import via `` ([script.js](script.js#L180-L200)) - **Blob API**: JSON export download ([script.js](script.js#L166-L177)) ### Cross-Page Communication Exclusively via localStorage keys. [index.html](index.html) writes `turnierplaner_data`, [planning.html](planning.html) reads it on load ([planning.js](planning.js#L12-L14)). ## Important Gotchas 1. **Field Count Logic**: `fieldCount` from user input = physical fields. Each field has 2 halves, so max `playingTeamsCount = fieldCount * 2` 2. **Field Numbering Offset**: Champions League fields start after Bundesliga fields ([planning.js](planning.js#L65-L69)) 3. **Round State Mismatch**: If `rotationState` deleted but `matchScores` exists, scores reference wrong teams 4. **Timer Doesn't Persist**: Timer state (`timerSeconds`, `isRunning`) not saved to localStorage 5. **Deletion Side Effects**: Deleting teams in [index.html](index.html) doesn't invalidate [planning.html](planning.html) rotation state