Festival Tracking App
Shape
HTML (index.html)
Shape
CSS (style.css)
/* =========================
Allgemeines Styling
========================= */
body {
font-family: Arial, sans-serif;
text-align: center;
background-color: #f5f5f5;
color: #444;
padding: 20px;
}
h1 {
margin-bottom: 20px;
}
.panel {
background-color: #fff;
padding: 15px 20px;
margin: 15px auto 25px auto;
border-radius: 10px;
max-width: 600px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
text-align: center;
}
.input-row {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
input {
padding: 5px;
margin: 5px;
font-size: 14px;
border-radius: 6px;
border: 1px solid #ccc;
}
select {
padding: 5px 10px;
margin: 5px;
font-size: 14px;
border-radius: 6px;
border: 1px solid #ccc;
min-width: 150px; /* Team-Auswahlbox größer */
}
button {
padding: 5px 10px;
margin: 5px;
cursor: pointer;
border-radius: 6px;
border: 1px solid #ccc;
background-color: #eee;
color: #333;
min-width: 80px; /* Einheitliche Button-Breite */
height: 35px; /* Einheitliche Höhe für Text und Emoji */
}
button:hover {
background-color: #ddd;
}
#player-buttons button,
#drink-buttons button,
#remove-drinks-buttons button {
margin: 3px;
font-size: 14px;
min-width: 80px;
height: 35px;
}
.selected {
background-color: #d1ffd1; /* grün hinterlegt für aktuell ausgewählt */
}
#current-player {
font-weight: bold;
color: green;
}
ul {
list-style-type: none;
padding: 0;
margin: 10px 0;
}
ul li {
padding: 6px 10px;
text-align: left;
position: relative;
background-color: #f0f0f0; /* neutrale Hintergrundfarbe für Balken */
border-radius: 6px;
margin-bottom: 5px;
overflow: hidden;
}
ul li .bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: #90caf9; /* dezenter Blau-Ton für Balken */
z-index: 0;
border-radius: 6px 0 0 6px;
}
ul li span {
position: relative;
z-index: 1;
}
/* Hervorhebung für ausgewählten Spieler */
ul li.selected {
background-color: #cce5ff; /* hellblauer Hintergrund */
font-weight: bold;
}
/* Hervorhebung für ausgewähltes Team */
#team-leaderboard li.selected {
background-color: #c8e6c9; /* hellgrün */
font-weight: bold;
}
/* Optional: Balken für ausgewählten Spieler/Team noch heller */
ul li.selected .bar {
opacity: 0.8;
}
/* =========================
Responsive Anpassungen
========================= */
@media (max-width: 600px) {
.input-row {
flex-direction: column;
align-items: center;
}
#player-buttons button,
#drink-buttons button,
#remove-drinks-buttons button {
width: 90%;
}
select {
width: 90%;
}
}
Shape
JavaScript (app.js)
// =========================
// KONSTANTEN
const DELETE_PASSWORD = "festival123"; // Admin Passwort
// =========================
// DATEN
let players = JSON.parse(localStorage.getItem('players') || '{}');
let drinks = JSON.parse(localStorage.getItem('drinks') || 'null') || {
"Bier": { points:1, emoji:"🍺" },
"Cocktail": { points:2, emoji:"🍹" },
"Softdrink": { points:0.5, emoji:"🥤" },
"Wasser": { points:0, emoji:"💧" }
};
let teams = JSON.parse(localStorage.getItem('teams') || 'null') || {
"Team Yeah":"Team Yeah",
"Team Whoo":"Team Whoo"
};
let selectedPlayer = "";
// =========================
// PLAYER FUNCTIONS
function addPlayer(){
const name = document.getElementById("player-name").value.trim();
const team = document.getElementById("player-team").value;
if(!name){alert("Bitte Namen eingeben"); return;}
if(!team){alert("Bitte Team wählen"); return;}
if(players[name]){alert("Teilnehmer existiert bereits!"); return;}
const password = prompt(`Passwort für ${name} festlegen:`);
if(!password){alert("Kein Passwort."); return;}
players[name] = { points:0, team:team, drinks:[], password };
document.getElementById("player-name").value = "";
savePlayers(); updatePlayerButtons(); updateLeaderboards(); updateRemoveDrinkButtons();
}
function deletePlayer(){
if(!selectedPlayer){alert("Bitte Teilnehmer auswählen"); return;}
const password = prompt("Admin-Passwort:");
if(password!==DELETE_PASSWORD){alert("Falsches Passwort."); return;}
delete players[selectedPlayer]; selectedPlayer="";
savePlayers(); updatePlayerButtons(); updateLeaderboards(); updateRemoveDrinkButtons();
}
function updatePlayerButtons(){
const container = document.getElementById("player-buttons");
container.innerHTML=" Festival Getränke Tracker
Festival Getränke Tracker
Teilnehmerverwaltung
Teams
Getränke
Getränke entfernen:
Rangliste Teilnehmer
Rangliste Teams
Teilnehmer auswählen:
"; Object.keys(players).forEach(name=>{ const btn = document.createElement("button"); btn.textContent = `${name} (${players[name].team})`; btn.onclick = ()=>selectPlayer(name); if(name===selectedPlayer) btn.classList.add("selected"); container.appendChild(btn); }); } function selectPlayer(name){ selectedPlayer=name; updatePlayerButtons(); updateLeaderboards(); updateRemoveDrinkButtons(); } // ========================= // DRINK FUNCTIONS function addDrink(drinkName){ if(!selectedPlayer || !drinks[drinkName]){alert("Fehler"); return;} players[selectedPlayer].points += drinks[drinkName].points; players[selectedPlayer].drinks.push(drinkName); savePlayers(); updateLeaderboards(true); updateRemoveDrinkButtons(); } function addOrEditDrink(){ const password = prompt("Admin-Passwort:"); if(password!==DELETE_PASSWORD){alert("Falsches Passwort"); return;} const name = prompt("Getränk Name:"); if(!name){alert("Ungültig"); return;} if(drinks[name]){ const points=parseFloat(prompt(`"${name}" existiert. Neue Punktzahl:`, drinks[name].points)); if(isNaN(points)){alert("Ungültige Zahl"); return;} const emoji=prompt(`Neues Emoji für "${name}":`, drinks[name].emoji); if(!emoji){alert("Kein Emoji"); return;} drinks[name]={points,emoji}; alert(`"${name}" aktualisiert`); } else { const points=parseFloat(prompt("Punkte:")); if(isNaN(points)){alert("Ungültige Zahl"); return;} const emoji=prompt("Emoji:"); if(!emoji){alert("Kein Emoji"); return;} drinks[name]={points,emoji}; alert(`"${name}" hinzugefügt`); } saveDrinks(); updateDrinkButtons(); } function updateDrinkButtons(){ const container=document.getElementById("drink-buttons"); container.innerHTML="Getränk auswählen:
"; Object.entries(drinks).forEach(([name,data])=>{ const btn=document.createElement("button"); btn.textContent=`${data.emoji} ${name} (+${data.points})`; btn.onclick=()=>addDrink(name); container.appendChild(btn); }); } // ========================= // REMOVE DRINK function updateRemoveDrinkButtons(){ const container=document.getElementById("remove-drinks-buttons"); container.innerHTML=""; if(!selectedPlayer || !players[selectedPlayer] || !players[selectedPlayer].drinks.length){ container.textContent="Keine Getränke zum Entfernen."; return; } players[selectedPlayer].drinks.forEach((drinkName,index)=>{ const btn=document.createElement("button"); btn.textContent=drinks[drinkName]?.emoji||""; btn.title=`${drinkName} (-${drinks[drinkName]?.points||0} Punkte)`; btn.onclick=()=>removeDrink(index); container.appendChild(btn); }); } function removeDrink(index){ if(!selectedPlayer) return; const inputPassword = prompt("Passwort für Getränke entfernen:"); if(inputPassword!==players[selectedPlayer].password){alert("Falsches Passwort."); return;} const drinkName = players[selectedPlayer].drinks[index]; const points = drinks[drinkName]?.points||0; players[selectedPlayer].points -= points; if(players[selectedPlayer].points<0) players[selectedPlayer].points=0; players[selectedPlayer].drinks.splice(index,1); savePlayers(); updateLeaderboards(true); updateRemoveDrinkButtons(); } // ========================= // TEAM FUNCTIONS function toggleTeamPanel(){ const panel = document.getElementById("team-panel"); if(panel.style.display==="none"){ const password = prompt("Admin-Passwort:"); if(password!==DELETE_PASSWORD){alert("Falsches Passwort."); return;} panel.style.display="block"; document.getElementById("team-yeah-name").value = teams["Team Yeah"]; document.getElementById("team-whoo-name").value = teams["Team Whoo"]; updateTeamSelectOptions(); } else panel.style.display="none"; } function updateTeamNames(){ const yeah=document.getElementById("team-yeah-name").value.trim(); const whoo=document.getElementById("team-whoo-name").value.trim(); if(yeah) teams["Team Yeah"]=yeah; if(whoo) teams["Team Whoo"]=whoo; localStorage.setItem('teams',JSON.stringify(teams)); updateTeamSelectOptions(); updatePlayerTeamDropdown(); updatePlayerButtons(); updateLeaderboards(); alert("Teamnamen aktualisiert!"); } function updateTeamSelectOptions(){ const select=document.getElementById("change-player-team-select"); select.innerHTML=""; Object.values(teams).forEach(teamName=>{ const opt=document.createElement("option"); opt.value=teamName; opt.textContent=teamName; select.appendChild(opt); }); } function updatePlayerTeamDropdown(){ const select=document.getElementById("player-team"); select.innerHTML=""; Object.values(teams).forEach(teamName=>{ const opt=document.createElement("option"); opt.value=teamName; opt.textContent=teamName; select.appendChild(opt); }); } function changePlayerTeam(){ if(!selectedPlayer){alert("Teilnehmer auswählen"); return;} const newTeam=document.getElementById("change-player-team-select").value; if(!newTeam){alert("Team auswählen"); return;} players[selectedPlayer].team=newTeam; savePlayers(); updatePlayerButtons(); updateLeaderboards(); alert(`${selectedPlayer} ist jetzt im Team ${newTeam}`); } // ========================= // LEADERBOARDS function updateLeaderboards(highlight=false){ const leaderboard=document.getElementById("leaderboard"); leaderboard.innerHTML=""; let maxPoints = 1; Object.values(players).forEach(p=>{if(p.points>maxPoints) maxPoints=p.points;}); // Teilnehmer-Rangliste Object.entries(players) .sort((a,b)=>b[1].points-a[1].points) .forEach(([name,data])=>{ let emojis = data.drinks.map(d=>drinks[d]?.emoji||"").join(" "); const li = document.createElement("li"); // Balken proportional const bar = document.createElement("div"); bar.className = "bar"; const widthPercent = (data.points/maxPoints)*100; bar.style.width = widthPercent + "%"; const span = document.createElement("span"); span.textContent = `${name} (${data.team}): ${data.points} Punkt(e) ${emojis}`; li.appendChild(bar); li.appendChild(span); if(highlight && name===selectedPlayer){li.classList.add("updated"); setTimeout(()=>li.classList.remove("updated"),1000);} if(name===selectedPlayer) li.classList.add("selected"); // Hervorhebung ausgewählter Spieler leaderboard.appendChild(li); }); // Team-Rangliste const teamLeaderboard=document.getElementById("team-leaderboard"); teamLeaderboard.innerHTML=""; const teamScores={}; Object.values(teams).forEach(t=>teamScores[t]=0); Object.values(players).forEach(p=>{if(p.team && teamScores[p.team]!==undefined) teamScores[p.team]+=p.points;}); let maxTeamPoints=1; Object.values(teamScores).forEach(p=>{if(p>maxTeamPoints) maxTeamPoints=p;}); Object.entries(teamScores) .sort((a,b)=>b[1]-a[1]) .forEach(([team,points])=>{ const li = document.createElement("li"); const bar = document.createElement("div"); bar.className = "bar"; bar.style.backgroundColor = "#a5d6a7"; bar.style.width = (points/maxTeamPoints)*100 + "%"; const span = document.createElement("span"); span.textContent = `${team}: ${points} Punkt(e)`; li.appendChild(bar); li.appendChild(span); // Hervorhebung Team des ausgewählten Spielers if(selectedPlayer && players[selectedPlayer].team === team){ li.classList.add("selected"); } teamLeaderboard.appendChild(li); }); } // ========================= // LOCALSTORAGE function savePlayers(){localStorage.setItem('players',JSON.stringify(players));} function saveDrinks(){localStorage.setItem('drinks',JSON.stringify(drinks));} // ========================= // INITIALIZE updatePlayerButtons(); updateDrinkButtons(); updateLeaderboards(); updateRemoveDrinkButtons(); updateTeamSelectOptions(); updatePlayerTeamDropdown(); ShapeFestival Getränke Tracker
Teilnehmerverwaltung
Teams
Getränke
Getränke entfernen:

