nvj-turnierplaner2/.github/copilot-instructions.md
2026-01-07 23:42:36 +01:00

113 lines
5.2 KiB
Markdown

# 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 `<input type="file">` ([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