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

5.2 KiB

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 + 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.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):

  • 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

// 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): 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): Winner gets |score1 - score2| + 2, loser gets -|score1 - score2|, tie = 0

Development Workflows

Running Locally

# 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 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)

Score Input Pattern (planning.js)

// Inline onchange in field card HTML
onchange="updateMatchScore('bundesliga', 1, this.value, otherInput.value)"

XSS Prevention

Use escapeHtml() (script.js) for user input in team names/clubs/mottos

Mobile-First CSS (styles.css)

  • viewport-fit=cover for iOS safe areas
  • Touch-optimized: min-height: 44px, touch-action: manipulation
  • Responsive grid: grid-template-columns: 1fr 1fr1fr on mobile

Integration Points & Dependencies

External Dependencies

None - pure vanilla JavaScript

Browser APIs

Cross-Page Communication

Exclusively via localStorage keys. index.html writes turnierplaner_data, planning.html reads it on load (planning.js).

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)
  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 doesn't invalidate planning.html rotation state