This repository has been archived on 2026-05-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tippspiel/prototyp_wm2026.html
2026-04-03 22:02:05 +02:00

1124 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WM 2026 Tippspiel GEALAN</title>
<style>
/* ── GEALAN Corporate Design ───────────────────────────── */
:root {
--dark-blue: #004891;
--light-blue: #0092D1;
--red: #CF073F;
--yellow: #F9AA2E;
--green: #2E7D32;
--text: #1B1B1B;
--gray-mid: #4C4C4C;
--gray-light: #818181;
--gray-border:#BEBEBE;
--gray-bg: #EBEBEB;
--white: #FFFFFF;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Calibri', 'Roboto', Arial, sans-serif;
background: var(--gray-bg);
color: var(--text);
font-size: 14px;
}
/* ── Header / Topbar ───────────────────────────────────── */
.app-header {
background: var(--white);
border-bottom: 3px solid var(--dark-blue);
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.app-header .logo-area {
display: flex;
align-items: center;
gap: 16px;
}
.app-header .logo-area .brand {
font-size: 18px;
font-weight: 700;
color: var(--dark-blue);
letter-spacing: 0.5px;
}
.app-header .logo-area .brand span {
color: var(--light-blue);
}
.app-header .logo-area .badge {
background: var(--yellow);
color: var(--text);
font-size: 11px;
font-weight: 700;
padding: 2px 8px;
border-radius: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.user-chip {
display: flex;
align-items: center;
gap: 8px;
background: var(--gray-bg);
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
color: var(--gray-mid);
}
.user-chip .avatar {
width: 28px; height: 28px;
background: var(--dark-blue);
color: white;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 700;
}
/* ── Navigation ────────────────────────────────────────── */
.nav {
background: var(--dark-blue);
display: flex;
gap: 0;
padding: 0 24px;
}
.nav-btn {
padding: 12px 20px;
color: rgba(255,255,255,0.7);
cursor: pointer;
font-size: 14px;
font-weight: 600;
border-bottom: 3px solid transparent;
transition: all 0.2s;
user-select: none;
display: flex;
align-items: center;
gap: 6px;
}
.nav-btn:hover { color: white; background: rgba(255,255,255,0.08); }
.nav-btn.active {
color: white;
border-bottom-color: var(--yellow);
}
.nav-btn .icon { font-size: 16px; }
/* ── Layout ────────────────────────────────────────────── */
.container {
max-width: 1100px;
margin: 0 auto;
padding: 24px 16px;
}
.page { display: none; }
.page.active { display: block; }
/* ── Cards ─────────────────────────────────────────────── */
.card {
background: var(--white);
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
overflow: hidden;
}
.card-header {
background: var(--dark-blue);
color: white;
padding: 14px 20px;
font-weight: 700;
font-size: 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-body { padding: 16px 20px; }
/* ── Dashboard ─────────────────────────────────────────── */
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: var(--white);
border-radius: 8px;
padding: 20px;
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
border-top: 4px solid var(--dark-blue);
}
.stat-card.blue { border-top-color: var(--dark-blue); }
.stat-card.light { border-top-color: var(--light-blue); }
.stat-card.yellow { border-top-color: var(--yellow); }
.stat-card.green { border-top-color: var(--green); }
.stat-card .stat-val {
font-size: 32px;
font-weight: 900;
color: var(--dark-blue);
line-height: 1;
}
.stat-card .stat-label {
font-size: 12px;
color: var(--gray-mid);
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 700px) {
.dashboard-grid { grid-template-columns: 1fr; }
}
/* ── Spielplan ─────────────────────────────────────────── */
.filter-bar {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.filter-btn {
padding: 6px 14px;
border: 2px solid var(--gray-border);
border-radius: 20px;
background: white;
color: var(--gray-mid);
cursor: pointer;
font-size: 13px;
font-weight: 600;
transition: all 0.15s;
}
.filter-btn:hover { border-color: var(--light-blue); color: var(--light-blue); }
.filter-btn.active { background: var(--dark-blue); border-color: var(--dark-blue); color: white; }
.match-day-header {
font-size: 12px;
font-weight: 700;
color: var(--gray-light);
text-transform: uppercase;
letter-spacing: 1px;
padding: 16px 0 6px;
}
.match-card {
background: var(--white);
border-radius: 8px;
margin-bottom: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.07);
border-left: 4px solid transparent;
transition: all 0.15s;
overflow: hidden;
}
.match-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.12); transform: translateY(-1px); }
.match-card.tipped { border-left-color: var(--light-blue); }
.match-card.finished { border-left-color: var(--gray-border); }
.match-card.correct { border-left-color: var(--green); }
.match-card.wrong { border-left-color: var(--red); }
.match-inner {
padding: 14px 16px;
display: grid;
grid-template-columns: 1fr auto 1fr auto;
align-items: center;
gap: 12px;
}
.match-team {
display: flex;
align-items: center;
gap: 10px;
}
.match-team.away { justify-content: flex-end; }
.match-team .flag {
width: 28px; height: 20px;
border-radius: 3px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.match-team .team-name {
font-weight: 700;
font-size: 14px;
}
.match-score-area {
text-align: center;
min-width: 90px;
}
.match-score {
font-size: 22px;
font-weight: 900;
color: var(--dark-blue);
letter-spacing: 1px;
}
.match-score.tbd { color: var(--gray-border); }
.match-time {
font-size: 11px;
color: var(--gray-light);
margin-top: 2px;
}
.match-group-badge {
font-size: 10px;
background: var(--gray-bg);
color: var(--gray-mid);
padding: 2px 6px;
border-radius: 4px;
font-weight: 700;
margin-top: 3px;
display: inline-block;
}
.match-tip-btn {
background: var(--dark-blue);
color: white;
border: none;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
transition: background 0.15s;
}
.match-tip-btn:hover { background: var(--light-blue); }
.match-tip-btn.tipped {
background: var(--light-blue);
}
.match-tip-btn.finished {
background: var(--gray-border);
color: var(--gray-mid);
cursor: default;
}
.my-tip-display {
font-size: 11px;
color: var(--light-blue);
font-weight: 700;
text-align: center;
margin-top: 2px;
}
.status-live {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--red);
color: white;
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
text-transform: uppercase;
}
.status-live::before {
content: '';
width: 6px; height: 6px;
background: white;
border-radius: 50%;
animation: blink 1s infinite;
}
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.3} }
/* ── Tipp-Modal ─────────────────────────────────────────── */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
z-index: 200;
align-items: center;
justify-content: center;
padding: 16px;
}
.modal-overlay.open { display: flex; }
.modal {
background: white;
border-radius: 12px;
width: 100%;
max-width: 420px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
animation: slideUp 0.2s ease;
}
@keyframes slideUp { from{transform:translateY(20px);opacity:0} to{transform:translateY(0);opacity:1} }
.modal-header {
background: var(--dark-blue);
color: white;
padding: 16px 20px;
font-weight: 700;
font-size: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-close { cursor: pointer; font-size: 20px; opacity: 0.7; transition: opacity 0.15s; }
.modal-close:hover { opacity: 1; }
.modal-body { padding: 24px 20px; }
.modal-teams {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.modal-team {
text-align: center;
flex: 1;
}
.modal-team .flag { font-size: 36px; display: block; }
.modal-team .name { font-weight: 700; font-size: 15px; margin-top: 6px; }
.modal-vs { font-size: 24px; font-weight: 900; color: var(--gray-border); padding: 0 12px; }
.modal-input-row {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 24px;
}
.score-input {
width: 72px; height: 72px;
border: 3px solid var(--gray-border);
border-radius: 12px;
font-size: 32px;
font-weight: 900;
text-align: center;
color: var(--dark-blue);
outline: none;
transition: border-color 0.15s;
}
.score-input:focus { border-color: var(--light-blue); }
.score-colon {
font-size: 32px;
font-weight: 900;
color: var(--dark-blue);
}
.modal-deadline {
text-align: center;
font-size: 12px;
color: var(--gray-light);
margin-bottom: 20px;
}
.modal-deadline strong { color: var(--yellow); }
.btn-primary {
width: 100%;
background: var(--dark-blue);
color: white;
border: none;
padding: 14px;
border-radius: 8px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s;
}
.btn-primary:hover { background: var(--light-blue); }
/* ── Rangliste ─────────────────────────────────────────── */
.leaderboard-table {
width: 100%;
border-collapse: collapse;
}
.leaderboard-table th {
background: var(--dark-blue);
color: white;
padding: 10px 14px;
text-align: left;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.leaderboard-table td {
padding: 12px 14px;
border-bottom: 1px solid var(--gray-bg);
font-size: 14px;
}
.leaderboard-table tr:nth-child(even) td { background: var(--gray-bg); }
.leaderboard-table tr.me td {
background: #E8F4FB !important;
font-weight: 700;
position: relative;
}
.leaderboard-table tr.me td:first-child::before {
content: '→';
position: absolute;
left: 4px;
color: var(--light-blue);
}
.rank-medal { font-size: 18px; }
.points-badge {
background: var(--dark-blue);
color: white;
padding: 3px 10px;
border-radius: 12px;
font-weight: 700;
font-size: 13px;
}
.trend-up { color: var(--green); font-weight: 700; }
.trend-down { color: var(--red); font-weight: 700; }
.trend-eq { color: var(--gray-light); }
/* ── Gruppen ───────────────────────────────────────────── */
.groups-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
.group-table { width: 100%; border-collapse: collapse; }
.group-table th {
text-align: left;
font-size: 11px;
color: var(--gray-light);
padding: 6px 8px;
border-bottom: 1px solid var(--gray-bg);
text-transform: uppercase;
}
.group-table td {
padding: 8px;
font-size: 13px;
border-bottom: 1px solid var(--gray-bg);
}
.group-table tr:last-child td { border-bottom: none; }
.qualified-1 td:first-child { border-left: 3px solid var(--green); }
.qualified-2 td:first-child { border-left: 3px solid var(--light-blue); }
.team-flag-name { display: flex; align-items: center; gap: 6px; }
.pts-bold { font-weight: 900; color: var(--dark-blue); }
/* ── Toast ─────────────────────────────────────────────── */
.toast {
position: fixed;
bottom: 24px;
right: 24px;
background: var(--dark-blue);
color: white;
padding: 12px 20px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
z-index: 999;
transform: translateY(80px);
opacity: 0;
transition: all 0.3s;
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}
.toast.show { transform: translateY(0); opacity: 1; }
.toast.success { background: var(--green); }
/* ── Info-Box ─────────────────────────────────────────────── */
.info-box {
background: #E8F4FB;
border-left: 4px solid var(--light-blue);
padding: 12px 16px;
border-radius: 0 8px 8px 0;
font-size: 13px;
color: var(--gray-mid);
margin-bottom: 20px;
}
.info-box strong { color: var(--dark-blue); }
.section-title {
font-size: 20px;
font-weight: 900;
color: var(--dark-blue);
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid var(--dark-blue);
}
</style>
</head>
<body>
<!-- ── Header ──────────────────────────────────────────── -->
<header class="app-header">
<div class="logo-area">
<div class="brand">GEALAN <span>Tippspiel</span></div>
<span class="badge">⚽ WM 2026</span>
</div>
<div class="user-chip">
<div class="avatar">RK</div>
<span>Ronny K.</span>
<strong style="color:var(--dark-blue);margin-left:4px">12 Pkt.</strong>
</div>
</header>
<!-- ── Navigation ──────────────────────────────────────── -->
<nav class="nav">
<div class="nav-btn active" onclick="showPage('dashboard')" data-page="dashboard">
<span class="icon">🏠</span> Dashboard
</div>
<div class="nav-btn" onclick="showPage('spielplan')" data-page="spielplan">
<span class="icon">📅</span> Spielplan &amp; Tippen
</div>
<div class="nav-btn" onclick="showPage('rangliste')" data-page="rangliste">
<span class="icon">🏆</span> Rangliste
</div>
<div class="nav-btn" onclick="showPage('gruppen')" data-page="gruppen">
<span class="icon">🗂</span> Gruppen
</div>
</nav>
<!-- ══════════════════════════════════════════════════════ -->
<!-- PAGE: Dashboard -->
<!-- ══════════════════════════════════════════════════════ -->
<div class="page active" id="page-dashboard">
<div class="container">
<div class="stats-row">
<div class="stat-card blue">
<div class="stat-val">12</div>
<div class="stat-label">Meine Punkte</div>
</div>
<div class="stat-card light">
<div class="stat-val">7.</div>
<div class="stat-label">Mein Platz</div>
</div>
<div class="stat-card yellow">
<div class="stat-val">18</div>
<div class="stat-label">Tipps abgegeben</div>
</div>
<div class="stat-card green">
<div class="stat-val">4</div>
<div class="stat-label">Exakte Treffer</div>
</div>
</div>
<div class="dashboard-grid">
<!-- Nächste Spiele -->
<div>
<div class="card">
<div class="card-header">
📅 Nächste Spiele zum Tippen
<span style="font-size:12px;font-weight:normal;opacity:0.8">3 offen</span>
</div>
<div class="card-body" style="padding:0">
<div id="next-matches-preview"></div>
</div>
</div>
</div>
<!-- Meine letzten Tipps -->
<div>
<div class="card">
<div class="card-header">📊 Meine letzten Auswertungen</div>
<div class="card-body" style="padding:0">
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--gray-bg)">
<th style="padding:8px 14px;font-size:12px;text-align:left;color:var(--gray-mid)">Spiel</th>
<th style="padding:8px 14px;font-size:12px;text-align:center;color:var(--gray-mid)">Tipp</th>
<th style="padding:8px 14px;font-size:12px;text-align:center;color:var(--gray-mid)">Erg.</th>
<th style="padding:8px 14px;font-size:12px;text-align:center;color:var(--gray-mid)">Pkt.</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid var(--gray-bg)">
<td style="padding:10px 14px;font-size:13px">🇩🇪 vs 🇯🇵</td>
<td style="padding:10px 14px;text-align:center;font-weight:700;color:var(--dark-blue)">2:1</td>
<td style="padding:10px 14px;text-align:center;font-weight:700">2:1</td>
<td style="padding:10px 14px;text-align:center"><span style="background:var(--green);color:white;padding:2px 8px;border-radius:10px;font-weight:700;font-size:13px">3</span></td>
</tr>
<tr style="background:var(--gray-bg);border-bottom:1px solid var(--white)">
<td style="padding:10px 14px;font-size:13px">🇧🇷 vs 🇫🇷</td>
<td style="padding:10px 14px;text-align:center;font-weight:700;color:var(--dark-blue)">1:0</td>
<td style="padding:10px 14px;text-align:center;font-weight:700">2:0</td>
<td style="padding:10px 14px;text-align:center"><span style="background:var(--light-blue);color:white;padding:2px 8px;border-radius:10px;font-weight:700;font-size:13px">1</span></td>
</tr>
<tr style="border-bottom:1px solid var(--gray-bg)">
<td style="padding:10px 14px;font-size:13px">🇦🇷 vs 🇲🇽</td>
<td style="padding:10px 14px;text-align:center;font-weight:700;color:var(--dark-blue)">0:1</td>
<td style="padding:10px 14px;text-align:center;font-weight:700">2:0</td>
<td style="padding:10px 14px;text-align:center"><span style="background:var(--red);color:white;padding:2px 8px;border-radius:10px;font-weight:700;font-size:13px">0</span></td>
</tr>
<tr style="background:var(--gray-bg)">
<td style="padding:10px 14px;font-size:13px">🇵🇹 vs 🇺🇸</td>
<td style="padding:10px 14px;text-align:center;font-weight:700;color:var(--dark-blue)">3:1</td>
<td style="padding:10px 14px;text-align:center;font-weight:700">3:1</td>
<td style="padding:10px 14px;text-align:center"><span style="background:var(--green);color:white;padding:2px 8px;border-radius:10px;font-weight:700;font-size:13px">3</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Top 5 Rangliste -->
<div style="margin-top:20px">
<div class="card">
<div class="card-header">
🏆 Aktuelle Rangliste (Top 5)
<span onclick="showPage('rangliste')" style="font-size:12px;cursor:pointer;opacity:0.8;text-decoration:underline">Alle anzeigen →</span>
</div>
<div class="card-body" style="padding:0">
<table class="leaderboard-table">
<thead><tr><th>#</th><th>Name</th><th>Tipps</th><th>Exakt</th><th>Punkte</th></tr></thead>
<tbody>
<tr><td><span class="rank-medal">🥇</span></td><td>Sandra W.</td><td>22</td><td>6</td><td><span class="points-badge">21</span></td></tr>
<tr><td><span class="rank-medal">🥈</span></td><td>Markus H.</td><td>20</td><td>5</td><td><span class="points-badge">18</span></td></tr>
<tr><td><span class="rank-medal">🥉</span></td><td>Julia B.</td><td>21</td><td>4</td><td><span class="points-badge">16</span></td></tr>
<tr><td style="padding-left:18px;color:var(--gray-mid)">4.</td><td>Thomas K.</td><td>19</td><td>4</td><td><span class="points-badge">15</span></td></tr>
<tr><td style="padding-left:18px;color:var(--gray-mid)">5.</td><td>Anna S.</td><td>20</td><td>3</td><td><span class="points-badge">13</span></td></tr>
</tbody>
</table>
<div style="padding:10px 16px;background:var(--gray-bg);border-top:1px solid var(--gray-border)">
<table class="leaderboard-table" style="background:transparent">
<tbody>
<tr class="me" style="background:transparent!important">
<td style="color:var(--light-blue);font-weight:900;padding-left:14px">7.</td>
<td style="font-weight:700">Ronny K. (ich)</td>
<td>18</td><td>4</td>
<td><span class="points-badge" style="background:var(--light-blue)">12</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════ -->
<!-- PAGE: Spielplan & Tippen -->
<!-- ══════════════════════════════════════════════════════ -->
<div class="page" id="page-spielplan">
<div class="container">
<div class="section-title">⚽ Spielplan &amp; Tippen</div>
<div class="info-box">
<strong>So funktioniert's:</strong> Klicke auf „Tippen" bei einem Spiel, um dein Ergebnis einzugeben. Tipps sind bis zum Anpfiff möglich. Exakter Tipp = <strong>3 Punkte</strong>, richtige Tendenz = <strong>1 Punkt</strong>.
</div>
<!-- Filter -->
<div class="filter-bar">
<button class="filter-btn active" onclick="filterMatches('all', this)">Alle Spiele</button>
<button class="filter-btn" onclick="filterMatches('open', this)">⏳ Noch offen</button>
<button class="filter-btn" onclick="filterMatches('tipped', this)">✅ Getippt</button>
<button class="filter-btn" onclick="filterMatches('finished', this)">✔ Abgeschlossen</button>
</div>
<div id="matches-list"></div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════ -->
<!-- PAGE: Rangliste -->
<!-- ══════════════════════════════════════════════════════ -->
<div class="page" id="page-rangliste">
<div class="container">
<div class="section-title">🏆 Gesamtrangliste</div>
<div class="info-box">
<strong>Stand nach Spieltag 3 der Gruppenphase.</strong> Rangliste aktualisiert sich automatisch nach Eintrag der Ergebnisse.
</div>
<div class="card">
<div class="card-header">
Rangliste — 47 Teilnehmer
<span style="font-size:12px;font-weight:normal">nach 22 Spielen</span>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th style="width:48px">#</th>
<th>Name</th>
<th>Abt.</th>
<th style="text-align:center">Tipps</th>
<th style="text-align:center">Exakt</th>
<th style="text-align:center">Tendenz</th>
<th style="text-align:center">Punkte</th>
<th style="text-align:center">Trend</th>
</tr>
</thead>
<tbody id="leaderboard-body"></tbody>
</table>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════ -->
<!-- PAGE: Gruppen -->
<!-- ══════════════════════════════════════════════════════ -->
<div class="page" id="page-gruppen">
<div class="container">
<div class="section-title">🗂 Gruppenphase WM 2026</div>
<div class="info-box">
<strong>WM 2026 Format:</strong> 48 Teams in 12 Gruppen à 4 Teams. Die Top 2 jeder Gruppe sowie die 8 besten Dritten ziehen ins Achtelfinale ein.
</div>
<div class="groups-grid" id="groups-grid"></div>
</div>
</div>
<!-- ── Tipp-Modal ──────────────────────────────────────── -->
<div class="modal-overlay" id="tip-modal" onclick="closeModal(event)">
<div class="modal">
<div class="modal-header">
<span id="modal-title">Tipp abgeben</span>
<span class="modal-close" onclick="closeTipModal()"></span>
</div>
<div class="modal-body">
<div class="modal-teams">
<div class="modal-team">
<span class="flag" id="modal-home-flag">🏳</span>
<div class="name" id="modal-home-name"></div>
</div>
<span class="modal-vs">:</span>
<div class="modal-team">
<span class="flag" id="modal-away-flag">🏳</span>
<div class="name" id="modal-away-name"></div>
</div>
</div>
<div class="modal-input-row">
<input class="score-input" type="number" id="tip-home" min="0" max="20" value="1" />
<span class="score-colon">:</span>
<input class="score-input" type="number" id="tip-away" min="0" max="20" value="1" />
</div>
<div class="modal-deadline">⏰ Deadline: <strong id="modal-deadline"></strong></div>
<button class="btn-primary" onclick="saveTip()">✅ Tipp speichern</button>
</div>
</div>
</div>
<!-- ── Toast ───────────────────────────────────────────── -->
<div class="toast" id="toast"></div>
<!-- ══════════════════════════════════════════════════════ -->
<script>
// ── Mock-Daten (football-data.org Format) ──────────────
const GROUPS = {
'A': ['Kanada', 'Argentinien', 'Peru', 'Neuseeland'],
'B': ['Mexiko', 'Deutschland', 'Japan', 'Senegal'],
'C': ['USA', 'Brasilien', 'Kolumbien', 'Marokko'],
'D': ['Spanien', 'Frankreich', 'Uruguay', 'Ägypten'],
'E': ['England', 'Portugal', 'Südkorea', 'Kamerun'],
'F': ['Niederlande', 'Belgien', 'Ecuador', 'Nigeria'],
'G': ['Italien', 'Kroatien', 'Chile', 'Algerien'],
'H': ['Schweiz', 'Serbien', 'Tunesien', 'Costa Rica'],
'I': ['Polen', 'Iran', 'Australien', 'Elfenbeinküste'],
'J': ['Dänemark', 'Ungarn', 'Katar', 'Jamaika'],
'K': ['Wales', 'Österreich', 'Saudi-Arabien', 'Panama'],
'L': ['Türkei', 'Ukraine', 'Albanien', 'Ghana'],
};
const FLAGS = {
'Kanada':'🇨🇦','Argentinien':'🇦🇷','Peru':'🇵🇪','Neuseeland':'🇳🇿',
'Mexiko':'🇲🇽','Deutschland':'🇩🇪','Japan':'🇯🇵','Senegal':'🇸🇳',
'USA':'🇺🇸','Brasilien':'🇧🇷','Kolumbien':'🇨🇴','Marokko':'🇲🇦',
'Spanien':'🇪🇸','Frankreich':'🇫🇷','Uruguay':'🇺🇾','Ägypten':'🇪🇬',
'England':'🏴󠁧󠁢󠁥󠁮󠁧󠁿','Portugal':'🇵🇹','Südkorea':'🇰🇷','Kamerun':'🇨🇲',
'Niederlande':'🇳🇱','Belgien':'🇧🇪','Ecuador':'🇪🇨','Nigeria':'🇳🇬',
'Italien':'🇮🇹','Kroatien':'🇭🇷','Chile':'🇨🇱','Algerien':'🇩🇿',
'Schweiz':'🇨🇭','Serbien':'🇷🇸','Tunesien':'🇹🇳','Costa Rica':'🇨🇷',
'Polen':'🇵🇱','Iran':'🇮🇷','Australien':'🇦🇺','Elfenbeinküste':'🇨🇮',
'Dänemark':'🇩🇰','Ungarn':'🇭🇺','Katar':'🇶🇦','Jamaika':'🇯🇲',
'Wales':'🏴󠁧󠁢󠁷󠁬󠁳󠁿','Österreich':'🇦🇹','Saudi-Arabien':'🇸🇦','Panama':'🇵🇦',
'Türkei':'🇹🇷','Ukraine':'🇺🇦','Albanien':'🇦🇱','Ghana':'🇬🇭',
};
// Spiele generieren (basierend auf football-data.org Struktur)
const MATCHES = [];
let matchId = 1;
const baseDate = new Date('2026-06-11T18:00:00Z');
Object.entries(GROUPS).forEach(([group, teams], gi) => {
const pairs = [
[0,1],[2,3],[0,2],[1,3],[0,3],[1,2]
];
pairs.forEach(([a, b], pi) => {
const d = new Date(baseDate);
d.setDate(d.getDate() + gi + pi * 2);
MATCHES.push({
id: matchId++,
utcDate: d.toISOString(),
status: pi < 2 ? 'FINISHED' : (pi === 2 ? 'SCHEDULED' : 'SCHEDULED'),
stage: 'GROUP_STAGE',
group: 'Group ' + group,
homeTeam: { name: teams[a], flag: FLAGS[teams[a]] || '🏳' },
awayTeam: { name: teams[b], flag: FLAGS[teams[b]] || '🏳' },
score: {
fullTime: pi < 2
? { homeTeam: Math.floor(Math.random()*4), awayTeam: Math.floor(Math.random()*3) }
: { homeTeam: null, awayTeam: null }
}
});
});
});
// Nutzer-Tipps (lokal gespeichert)
const myTips = {};
// Einige Demo-Tipps
MATCHES.filter(m => m.status === 'FINISHED').slice(0, 18).forEach(m => {
myTips[m.id] = {
home: Math.floor(Math.random() * 4),
away: Math.floor(Math.random() * 3)
};
});
// ── Leaderboard-Daten ───────────────────────────────────
const PLAYERS = [
{ rank: 1, name: 'Sandra W.', dept: 'Vertrieb', tips: 22, exact: 6, tendency: 8, pts: 21, trend: 'eq' },
{ rank: 2, name: 'Markus H.', dept: 'Technik', tips: 20, exact: 5, tendency: 7, pts: 18, trend: 'up' },
{ rank: 3, name: 'Julia B.', dept: 'Marketing', tips: 21, exact: 4, tendency: 8, pts: 16, trend: 'down' },
{ rank: 4, name: 'Thomas K.', dept: 'Einkauf', tips: 19, exact: 4, tendency: 7, pts: 15, trend: 'up' },
{ rank: 5, name: 'Anna S.', dept: 'HR', tips: 20, exact: 3, tendency: 8, pts: 13, trend: 'eq' },
{ rank: 6, name: 'Klaus M.', dept: 'IT', tips: 18, exact: 3, tendency: 7, pts: 13, trend: 'up' },
{ rank: 7, name: 'Ronny K.', dept: 'IT', tips: 18, exact: 4, tendency: 4, pts: 12, trend: 'up', isMe: true },
{ rank: 8, name: 'Laura P.', dept: 'Finanzen', tips: 17, exact: 3, tendency: 6, pts: 11, trend: 'down' },
{ rank: 9, name: 'Stefan R.', dept: 'Technik', tips: 16, exact: 2, tendency: 7, pts: 11, trend: 'eq' },
{ rank: 10, name: 'Maria L.', dept: 'Vertrieb', tips: 15, exact: 2, tendency: 6, pts: 9, trend: 'down' },
{ rank: 11, name: 'Felix N.', dept: 'Marketing', tips: 14, exact: 1, tendency: 6, pts: 8, trend: 'eq' },
{ rank: 12, name: 'Eva H.', dept: 'HR', tips: 13, exact: 1, tendency: 5, pts: 7, trend: 'up' },
];
// ── Helpers ─────────────────────────────────────────────
function formatDate(isoStr) {
const d = new Date(isoStr);
return d.toLocaleDateString('de-DE', { weekday:'short', day:'2-digit', month:'2-digit' })
+ ', ' + d.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' }) + ' Uhr';
}
function calcPoints(tip, result) {
if (!tip || result.homeTeam === null) return null;
if (tip.home === result.homeTeam && tip.away === result.awayTeam) return 3;
const tipTend = tip.home > tip.away ? 1 : tip.home < tip.away ? -1 : 0;
const resTend = result.homeTeam > result.awayTeam ? 1 : result.homeTeam < result.awayTeam ? -1 : 0;
return tipTend === resTend ? 1 : 0;
}
// ── Page Navigation ─────────────────────────────────────
function showPage(name) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
document.getElementById('page-' + name).classList.add('active');
document.querySelector(`[data-page="${name}"]`).classList.add('active');
}
// ── Render: Spielplan ────────────────────────────────────
let activeFilter = 'all';
function filterMatches(filter, btn) {
activeFilter = filter;
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderMatches();
}
function renderMatches() {
let filtered = MATCHES;
if (activeFilter === 'open') filtered = MATCHES.filter(m => m.status === 'SCHEDULED' && !myTips[m.id]);
if (activeFilter === 'tipped') filtered = MATCHES.filter(m => myTips[m.id]);
if (activeFilter === 'finished') filtered = MATCHES.filter(m => m.status === 'FINISHED');
const list = document.getElementById('matches-list');
if (!filtered.length) {
list.innerHTML = '<div style="text-align:center;padding:40px;color:var(--gray-light)">Keine Spiele in dieser Kategorie.</div>';
return;
}
// Nach Datum gruppieren
const byDate = {};
filtered.forEach(m => {
const dateKey = new Date(m.utcDate).toLocaleDateString('de-DE', { weekday:'long', day:'2-digit', month:'long', year:'numeric' });
if (!byDate[dateKey]) byDate[dateKey] = [];
byDate[dateKey].push(m);
});
let html = '';
Object.entries(byDate).forEach(([date, matches]) => {
html += `<div class="match-day-header">${date}</div>`;
matches.forEach(m => {
const tip = myTips[m.id];
const res = m.score.fullTime;
const pts = calcPoints(tip, res);
let cardClass = '';
if (m.status === 'FINISHED') cardClass = pts === 3 ? 'correct' : pts === 1 ? 'tipped' : pts === 0 ? 'wrong' : 'finished';
else if (tip) cardClass = 'tipped';
const btnText = m.status === 'FINISHED' ? 'Beendet' : (tip ? `${tip.home}:${tip.away}` : 'Tippen');
const btnClass = m.status === 'FINISHED' ? 'finished' : (tip ? 'tipped' : '');
const scoreHtml = m.status === 'FINISHED'
? `<div class="match-score">${res.homeTeam}:${res.awayTeam}</div>`
: `<div class="match-score tbd">-:-</div>`;
const ptsHtml = (m.status === 'FINISHED' && tip)
? `<div style="font-size:11px;margin-top:3px">
${pts === 3 ? '<span style="color:var(--green);font-weight:700">+3 Pkt. ✓✓</span>'
: pts === 1 ? '<span style="color:var(--light-blue);font-weight:700">+1 Pkt. ✓</span>'
: '<span style="color:var(--red);font-weight:700">0 Pkt. ✗</span>'}</div>`
: '';
const tipDisplay = tip && m.status !== 'FINISHED'
? `<div class="my-tip-display">Mein Tipp: ${tip.home}:${tip.away}</div>` : '';
html += `
<div class="match-card ${cardClass}">
<div class="match-inner">
<div class="match-team">
<span class="flag">${m.homeTeam.flag}</span>
<span class="team-name">${m.homeTeam.name}</span>
</div>
<div class="match-score-area">
${scoreHtml}
<div class="match-time">${formatDate(m.utcDate)}</div>
<div><span class="match-group-badge">${m.group}</span></div>
${ptsHtml}
${tipDisplay}
</div>
<div class="match-team away">
<span class="team-name">${m.awayTeam.name}</span>
<span class="flag">${m.awayTeam.flag}</span>
</div>
<div>
${m.status !== 'FINISHED'
? `<button class="match-tip-btn ${btnClass}" onclick="openTipModal(${m.id})">${btnText}</button>`
: `<button class="match-tip-btn finished" disabled>Beendet</button>`}
</div>
</div>
</div>`;
});
});
list.innerHTML = html;
}
// ── Render: Dashboard preview ────────────────────────────
function renderDashboardPreview() {
const upcoming = MATCHES.filter(m => m.status === 'SCHEDULED').slice(0, 3);
const el = document.getElementById('next-matches-preview');
if (!upcoming.length) { el.innerHTML = '<div style="padding:16px;color:var(--gray-light)">Keine offenen Spiele.</div>'; return; }
let html = '';
upcoming.forEach(m => {
const tip = myTips[m.id];
html += `
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--gray-bg)">
<span style="font-size:13px">${m.homeTeam.flag} ${m.homeTeam.name} vs ${m.awayTeam.flag} ${m.awayTeam.name}</span>
<button class="match-tip-btn ${tip ? 'tipped' : ''}" style="font-size:11px;padding:6px 10px" onclick="showPage('spielplan');openTipModal(${m.id})">
${tip ? `${tip.home}:${tip.away}` : 'Tippen'}
</button>
</div>`;
});
el.innerHTML = html;
}
// ── Render: Rangliste ────────────────────────────────────
function renderLeaderboard() {
const tbody = document.getElementById('leaderboard-body');
tbody.innerHTML = PLAYERS.map(p => `
<tr ${p.isMe ? 'class="me"' : ''}>
<td>${p.rank <= 3 ? ['🥇','🥈','🥉'][p.rank-1] : `<span style="padding-left:6px;color:var(--gray-mid)">${p.rank}.</span>`}</td>
<td>${p.name}${p.isMe ? ' <span style="font-size:11px;color:var(--light-blue)">(ich)</span>' : ''}</td>
<td style="color:var(--gray-mid)">${p.dept}</td>
<td style="text-align:center">${p.tips}</td>
<td style="text-align:center">${p.exact}</td>
<td style="text-align:center">${p.tendency}</td>
<td style="text-align:center"><span class="points-badge" ${p.isMe ? 'style="background:var(--light-blue)"' : ''}>${p.pts}</span></td>
<td style="text-align:center">
${p.trend === 'up' ? '<span class="trend-up">▲</span>'
: p.trend === 'down' ? '<span class="trend-down">▼</span>'
: '<span class="trend-eq"></span>'}
</td>
</tr>`).join('');
}
// ── Render: Gruppen ──────────────────────────────────────
function renderGroups() {
const grid = document.getElementById('groups-grid');
const groupStandings = {};
MATCHES.filter(m => m.status === 'FINISHED').forEach(m => {
const g = m.group;
if (!groupStandings[g]) groupStandings[g] = {};
[m.homeTeam, m.awayTeam].forEach(t => {
if (!groupStandings[g][t.name]) groupStandings[g][t.name] = { name: t.name, flag: t.flag, played:0, won:0, draw:0, lost:0, gf:0, ga:0, pts:0 };
});
const hs = groupStandings[g][m.homeTeam.name];
const as = groupStandings[g][m.awayTeam.name];
const hg = m.score.fullTime.homeTeam, ag = m.score.fullTime.awayTeam;
hs.played++; as.played++;
hs.gf += hg; hs.ga += ag;
as.gf += ag; as.ga += hg;
if (hg > ag) { hs.won++; hs.pts+=3; as.lost++; }
else if (hg < ag) { as.won++; as.pts+=3; hs.lost++; }
else { hs.draw++; hs.pts++; as.draw++; as.pts++; }
});
grid.innerHTML = Object.entries(GROUPS).map(([g, teams]) => {
const key = 'Group ' + g;
let standings = teams.map(t => groupStandings[key]?.[t] || { name:t, flag:FLAGS[t]||'🏳', played:0, won:0, draw:0, lost:0, gf:0, ga:0, pts:0 });
standings.sort((a,b) => b.pts - a.pts || (b.gf-b.ga) - (a.gf-a.ga) || b.gf - a.gf);
return `
<div class="card">
<div class="card-header">Gruppe ${g}</div>
<div class="card-body" style="padding:8px">
<table class="group-table">
<thead><tr><th>Team</th><th>Sp</th><th>S</th><th>U</th><th>N</th><th>Tore</th><th>Pkt</th></tr></thead>
<tbody>${standings.map((t,i) => `
<tr class="${i===0?'qualified-1':i===1?'qualified-2':''}">
<td><div class="team-flag-name">${t.flag} ${t.name}</div></td>
<td>${t.played}</td><td>${t.won}</td><td>${t.draw}</td><td>${t.lost}</td>
<td style="color:var(--gray-mid)">${t.gf}:${t.ga}</td>
<td><span class="pts-bold">${t.pts}</span></td>
</tr>`).join('')}
</tbody>
</table>
<div style="font-size:10px;color:var(--gray-light);padding:6px 4px 2px">
<span style="border-left:3px solid var(--green);padding-left:4px;margin-right:8px">Gruppensieger</span>
<span style="border-left:3px solid var(--light-blue);padding-left:4px">Gruppenzweiter</span>
</div>
</div>
</div>`;
}).join('');
}
// ── Tipp-Modal ───────────────────────────────────────────
let activeTipMatchId = null;
function openTipModal(matchId) {
const m = MATCHES.find(x => x.id === matchId);
if (!m || m.status === 'FINISHED') return;
activeTipMatchId = matchId;
document.getElementById('modal-home-flag').textContent = m.homeTeam.flag;
document.getElementById('modal-away-flag').textContent = m.awayTeam.flag;
document.getElementById('modal-home-name').textContent = m.homeTeam.name;
document.getElementById('modal-away-name').textContent = m.awayTeam.name;
document.getElementById('modal-title').textContent = m.group + ' — Tipp abgeben';
document.getElementById('modal-deadline').textContent = formatDate(m.utcDate);
const tip = myTips[matchId];
document.getElementById('tip-home').value = tip ? tip.home : 1;
document.getElementById('tip-away').value = tip ? tip.away : 1;
document.getElementById('tip-modal').classList.add('open');
setTimeout(() => document.getElementById('tip-home').focus(), 100);
}
function closeModal(e) {
if (e.target === document.getElementById('tip-modal')) closeTipModal();
}
function closeTipModal() {
document.getElementById('tip-modal').classList.remove('open');
}
function saveTip() {
const h = parseInt(document.getElementById('tip-home').value);
const a = parseInt(document.getElementById('tip-away').value);
if (isNaN(h) || isNaN(a) || h < 0 || a < 0) {
showToast('Bitte gültige Zahlen eingeben.', false); return;
}
myTips[activeTipMatchId] = { home: h, away: a };
closeTipModal();
renderMatches();
renderDashboardPreview();
showToast(`✅ Tipp gespeichert: ${h}:${a}`, true);
}
// ── Toast ────────────────────────────────────────────────
function showToast(msg, success=true) {
const t = document.getElementById('toast');
t.textContent = msg;
t.className = 'toast show' + (success ? ' success' : '');
setTimeout(() => t.classList.remove('show'), 3000);
}
// ── Init ─────────────────────────────────────────────────
renderMatches();
renderDashboardPreview();
renderLeaderboard();
renderGroups();
</script>
</body>
</html>