Bei neuem Service Worker wird die Seite automatisch neu geladen. Kein Popup mehr für die User.
1205 lines
38 KiB
HTML
1205 lines
38 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>JGA-Trinkspiel — Prag 2026</title>
|
|
<link rel="manifest" href="manifest.json">
|
|
<meta name="theme-color" content="#3b82f6">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="apple-mobile-web-app-title" content="JGA Prag">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<script>
|
|
(function() {
|
|
const saved = localStorage.getItem('jga-theme');
|
|
document.documentElement.setAttribute('data-theme', saved || 'dark');
|
|
})();
|
|
</script>
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🃏</text></svg>">
|
|
<style>
|
|
/* ====== Theme-Variablen ====== */
|
|
:root, [data-theme="dark"] {
|
|
--bg: #1c1c1f;
|
|
--card-bg: #26262a;
|
|
--card-bg-2: #2e2e33;
|
|
--card-border: #353539;
|
|
--text: #e8e8ea;
|
|
--text-muted: #999;
|
|
--text-strong: #fff;
|
|
--accent: #3b82f6;
|
|
--accent-text: #60a5fa;
|
|
--danger: #ef4444;
|
|
--success: #22c55e;
|
|
--warn: #f59e0b;
|
|
--shadow: 0 4px 16px rgba(0,0,0,0.35);
|
|
--shadow-strong: 0 8px 32px rgba(0,0,0,0.5);
|
|
--card-back: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%);
|
|
--card-back-pattern: rgba(255,255,255,0.08);
|
|
}
|
|
[data-theme="light"] {
|
|
--bg: #f5f5f7;
|
|
--card-bg: #ffffff;
|
|
--card-bg-2: #f8f8fb;
|
|
--card-border: #e0e0e3;
|
|
--text: #1a1a1a;
|
|
--text-muted: #666;
|
|
--text-strong: #000;
|
|
--accent: #2563eb;
|
|
--accent-text: #2563eb;
|
|
--danger: #dc2626;
|
|
--success: #16a34a;
|
|
--warn: #d97706;
|
|
--shadow: 0 4px 16px rgba(0,0,0,0.08);
|
|
--shadow-strong: 0 8px 32px rgba(0,0,0,0.15);
|
|
--card-back: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%);
|
|
--card-back-pattern: rgba(255,255,255,0.15);
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body {
|
|
margin: 0; padding: 0;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
min-height: 100vh;
|
|
transition: background 0.3s, color 0.3s;
|
|
}
|
|
.container { max-width: 1100px; margin: 0 auto; padding: 24px 24px 60px; }
|
|
h1 {
|
|
font-size: 36px;
|
|
margin: 0 0 8px 0;
|
|
color: var(--text-strong);
|
|
font-weight: 700;
|
|
}
|
|
.subtitle { color: var(--text-muted); margin-bottom: 28px; font-size: 15px; }
|
|
.back-link {
|
|
display: inline-block;
|
|
margin-bottom: 16px;
|
|
color: var(--accent-text);
|
|
text-decoration: none;
|
|
font-size: 14px;
|
|
}
|
|
.back-link:hover { text-decoration: underline; }
|
|
|
|
/* Theme-Toggle */
|
|
.home-btn {
|
|
position: fixed;
|
|
top: 16px; left: 16px;
|
|
z-index: 1000;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
padding: 10px 16px;
|
|
border-radius: 22px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
transition: all 0.2s;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
@media (max-width: 768px) {
|
|
.home-btn { top: 10px; left: 10px; padding: 8px 12px; font-size: 13px; }
|
|
.theme-toggle { top: 10px; right: 10px; width: 38px; height: 38px; font-size: 18px; }
|
|
}
|
|
.home-btn:hover {
|
|
transform: translateX(-2px);
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
}
|
|
.theme-toggle {
|
|
position: fixed;
|
|
top: 16px;
|
|
right: 16px;
|
|
width: 44px; height: 44px;
|
|
border-radius: 50%;
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
color: var(--text);
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
z-index: 100;
|
|
box-shadow: var(--shadow);
|
|
transition: all 0.2s;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.theme-toggle:hover {
|
|
transform: rotate(15deg) scale(1.1);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
/* ===== Layout ===== */
|
|
.layout {
|
|
display: grid;
|
|
grid-template-columns: 260px 1fr 280px;
|
|
gap: 20px;
|
|
}
|
|
@media (max-width: 900px) {
|
|
.layout { grid-template-columns: 1fr; }
|
|
}
|
|
|
|
.panel {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 12px;
|
|
padding: 18px;
|
|
}
|
|
.panel h2 {
|
|
margin: 0 0 14px;
|
|
font-size: 15px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
color: var(--text-muted);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ===== Spiele-Auswahl ===== */
|
|
.game-list { display: flex; flex-direction: column; gap: 8px; }
|
|
.game-btn {
|
|
text-align: left;
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
color: var(--text);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.game-btn:hover {
|
|
border-color: var(--accent);
|
|
transform: translateX(2px);
|
|
}
|
|
.game-btn.active {
|
|
border-color: var(--accent);
|
|
background: rgba(59, 130, 246, 0.12);
|
|
color: var(--text-strong);
|
|
font-weight: 600;
|
|
}
|
|
.game-btn .icon { font-size: 22px; }
|
|
|
|
/* ===== Spieler ===== */
|
|
.player-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
|
|
.player {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 8px 10px;
|
|
font-size: 14px;
|
|
}
|
|
.player .avatar { font-size: 22px; }
|
|
.player .name { flex: 1; font-weight: 500; }
|
|
.player .drinks {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border-radius: 12px;
|
|
padding: 2px 10px;
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
min-width: 32px;
|
|
text-align: center;
|
|
}
|
|
.player.drunk .drinks { background: var(--danger); }
|
|
.player .remove {
|
|
background: transparent;
|
|
border: 0;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
padding: 0 4px;
|
|
}
|
|
.player .remove:hover { color: var(--danger); }
|
|
|
|
.add-player {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
.add-player input {
|
|
flex: 1;
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 8px 10px;
|
|
color: var(--text);
|
|
font-size: 14px;
|
|
}
|
|
.add-player input:focus { outline: 2px solid var(--accent); }
|
|
.add-player button {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* ===== Mitte: Karten-Deck ===== */
|
|
.center {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
.game-info {
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
max-width: 480px;
|
|
}
|
|
.game-info h3 {
|
|
margin: 0 0 6px;
|
|
font-size: 22px;
|
|
color: var(--text-strong);
|
|
}
|
|
.game-info p {
|
|
margin: 0;
|
|
color: var(--text-muted);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.deck-area {
|
|
position: relative;
|
|
width: 240px;
|
|
height: 336px;
|
|
margin: 20px 0 32px;
|
|
perspective: 1200px;
|
|
}
|
|
.card-wrap {
|
|
position: absolute;
|
|
top: 0; left: 0;
|
|
width: 100%; height: 100%;
|
|
transform-style: preserve-3d;
|
|
transition: transform 0.7s cubic-bezier(0.4, 0.0, 0.2, 1);
|
|
cursor: pointer;
|
|
}
|
|
.card-wrap.flipped { transform: rotateY(180deg); }
|
|
.card-face {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 16px;
|
|
backface-visibility: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-shadow: var(--shadow-strong);
|
|
overflow: hidden;
|
|
}
|
|
.card-back {
|
|
background: var(--card-back);
|
|
border: 6px solid #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
.card-back::before, .card-back::after {
|
|
content: '';
|
|
position: absolute;
|
|
background: var(--card-back-pattern);
|
|
}
|
|
.card-back::before {
|
|
width: 70%; height: 70%;
|
|
border-radius: 50%;
|
|
}
|
|
.card-back::after {
|
|
width: 90%; height: 2px;
|
|
top: 50%; left: 5%;
|
|
transform: rotate(45deg);
|
|
box-shadow:
|
|
0 -80px 0 var(--card-back-pattern),
|
|
0 -160px 0 var(--card-back-pattern),
|
|
0 80px 0 var(--card-back-pattern),
|
|
0 160px 0 var(--card-back-pattern);
|
|
}
|
|
.card-back .logo {
|
|
font-size: 64px;
|
|
z-index: 1;
|
|
filter: drop-shadow(0 2px 8px rgba(0,0,0,0.3));
|
|
}
|
|
|
|
.card-front {
|
|
transform: rotateY(180deg);
|
|
background: var(--card-bg);
|
|
border: 2px solid var(--card-border);
|
|
padding: 20px;
|
|
text-align: center;
|
|
}
|
|
.card-front.red { color: #dc2626; }
|
|
.card-front.black { color: #1a1a1a; }
|
|
[data-theme="dark"] .card-front.black { color: #fff; }
|
|
.card-front .suit {
|
|
font-size: 48px;
|
|
line-height: 1;
|
|
}
|
|
.card-front .rank {
|
|
font-size: 72px;
|
|
font-weight: 800;
|
|
line-height: 1;
|
|
margin: 8px 0;
|
|
}
|
|
.card-front .action {
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
line-height: 1.4;
|
|
color: var(--text);
|
|
margin-top: 12px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--card-border);
|
|
}
|
|
.card-front .who {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.deck-stack {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
}
|
|
.deck-stack::before, .deck-stack::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: var(--card-back);
|
|
border: 6px solid #fff;
|
|
border-radius: 16px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.deck-stack::before {
|
|
transform: translate(8px, 8px);
|
|
opacity: 0.7;
|
|
}
|
|
.deck-stack::after {
|
|
transform: translate(4px, 4px);
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.deck-controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
.btn {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border: 0;
|
|
border-radius: 10px;
|
|
padding: 12px 22px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.btn:hover { transform: translateY(-1px); }
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
.btn-secondary {
|
|
background: var(--card-bg-2);
|
|
color: var(--text);
|
|
border: 1px solid var(--card-border);
|
|
}
|
|
.btn-danger { background: var(--danger); }
|
|
|
|
.draw-status {
|
|
margin-top: 14px;
|
|
color: var(--text-muted);
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ===== Regeln / Stats ===== */
|
|
.rules-list { list-style: none; padding: 0; margin: 0; }
|
|
.rules-list li {
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-left: 3px solid var(--warn);
|
|
border-radius: 8px;
|
|
padding: 10px 12px;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: flex-start;
|
|
}
|
|
.rules-list li .rule-text { flex: 1; }
|
|
.rules-list li .rule-meta {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-top: 2px;
|
|
}
|
|
.rules-list li button {
|
|
background: transparent;
|
|
border: 0;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
}
|
|
.rules-list li button:hover { color: var(--danger); }
|
|
|
|
.stats {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px;
|
|
}
|
|
.stat {
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
text-align: center;
|
|
}
|
|
.stat .num {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--accent-text);
|
|
}
|
|
.stat .label {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* History */
|
|
.history {
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
margin-top: 10px;
|
|
}
|
|
.history-item {
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 6px;
|
|
padding: 6px 10px;
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
.history-item .mini-card {
|
|
font-size: 16px;
|
|
width: 28px;
|
|
text-align: center;
|
|
}
|
|
.history-item.red { color: #dc2626; }
|
|
.history-item.black { color: var(--text); }
|
|
[data-theme="dark"] .history-item.black { color: #fff; }
|
|
.history-item .who-text { color: var(--text-muted); margin-left: auto; font-size: 12px; }
|
|
|
|
/* King-Becher */
|
|
.king-meter {
|
|
margin: 16px 0 4px;
|
|
background: var(--card-bg-2);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
}
|
|
.king-meter .label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 6px;
|
|
}
|
|
.king-bar {
|
|
height: 8px;
|
|
background: var(--card-border);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
}
|
|
.king-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--warn) 0%, var(--danger) 100%);
|
|
transition: width 0.4s;
|
|
}
|
|
|
|
/* Modal für Spieler-Auswahl */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,0.6);
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 200;
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
.modal-overlay.open { display: flex; }
|
|
.modal {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
max-width: 400px;
|
|
width: 90%;
|
|
box-shadow: var(--shadow-strong);
|
|
}
|
|
.modal h3 { margin: 0 0 16px; color: var(--text-strong); }
|
|
.modal-actions { display: flex; gap: 10px; margin-top: 18px; }
|
|
.modal-actions .btn { flex: 1; }
|
|
|
|
/* Empty state */
|
|
.empty {
|
|
color: var(--text-muted);
|
|
font-size: 13px;
|
|
text-align: center;
|
|
padding: 16px;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Footer */
|
|
.footer-note {
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
margin-top: 40px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<a href="index.html" class="home-btn" title="Zurück zur Startseite">🏠 Startseite</a>
|
|
<button class="theme-toggle" id="themeToggle" aria-label="Theme wechseln">🌙</button>
|
|
|
|
<div class="container">
|
|
<a href="index.html" class="back-link">← Zurück zur Übersicht</a>
|
|
<h1>🃏 JGA-Trinkspiel</h1>
|
|
<p class="subtitle">Klassische Trinkspiele für unterwegs — fair, ohne Spielkarten, ohne Sucheritis</p>
|
|
|
|
<div class="layout">
|
|
|
|
<!-- Linke Spalte: Spiel-Auswahl -->
|
|
<aside class="panel">
|
|
<h2>Spiel wählen</h2>
|
|
<div class="game-list" id="gameList">
|
|
<button class="game-btn active" data-game="kings"><span class="icon">👑</span><span>Kings</span></button>
|
|
<button class="game-btn" data-game="ride"><span class="icon">🚌</span><span>Ride the Bus</span></button>
|
|
<button class="game-btn" data-game="ring"><span class="icon">🔥</span><span>Ring of Fire</span></button>
|
|
<button class="game-btn" data-game="never"><span class="icon">🚫</span><span>Ich hab noch nie</span></button>
|
|
<button class="game-btn" data-game="truth"><span class="icon">🎯</span><span>Wahrheit/Pflicht</span></button>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Mitte: Karten-Deck -->
|
|
<main class="center">
|
|
<div class="game-info">
|
|
<h3 id="gameTitle">👑 Kings</h3>
|
|
<p id="gameDescription">Ziehe Karten, führe Aktionen aus. Wer alle 4 Könige zieht, trinkt den König-Becher leer.</p>
|
|
</div>
|
|
|
|
<div class="deck-area" id="deckArea">
|
|
<div class="deck-stack"></div>
|
|
<div class="card-wrap" id="cardWrap">
|
|
<div class="card-face card-back">
|
|
<span class="logo">🃏</span>
|
|
</div>
|
|
<div class="card-face card-front" id="cardFront">
|
|
<div class="suit" id="cardSuit">♠</div>
|
|
<div class="rank" id="cardRank">A</div>
|
|
<div class="action" id="cardAction">Trinke 1 Schluck</div>
|
|
<div class="who" id="cardWho"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ring of Fire: King-Becher Meter -->
|
|
<div class="king-meter" id="kingMeter" style="display:none;">
|
|
<div class="label">
|
|
<span>👑 König-Becher</span>
|
|
<span id="kingCount">0 / 4</span>
|
|
</div>
|
|
<div class="king-bar"><div class="king-fill" id="kingFill" style="width:0%"></div></div>
|
|
</div>
|
|
|
|
<div class="deck-controls">
|
|
<button class="btn" id="drawBtn">🎴 Karte ziehen</button>
|
|
<button class="btn btn-secondary" id="resetBtn">🔄 Neues Spiel</button>
|
|
</div>
|
|
<div class="draw-status" id="drawStatus">Karten im Deck: 52</div>
|
|
|
|
<!-- History -->
|
|
<div style="width:100%; max-width:600px;">
|
|
<h3 style="font-size:14px; text-transform:uppercase; letter-spacing:0.5px; color:var(--text-muted); margin:30px 0 8px;">📜 Gezogene Karten</h3>
|
|
<div class="history" id="history">
|
|
<div class="empty">Noch keine Karten gezogen</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Rechte Spalte: Spieler + Regeln + Stats -->
|
|
<aside>
|
|
<div class="panel" style="margin-bottom:16px;">
|
|
<h2>👥 Spieler</h2>
|
|
<div class="player-list" id="playerList"></div>
|
|
<div class="add-player">
|
|
<input type="text" id="playerInput" placeholder="Name..." maxlength="20">
|
|
<button id="addPlayerBtn">+</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel" style="margin-bottom:16px;">
|
|
<h2>📜 Aktive Regeln</h2>
|
|
<ul class="rules-list" id="rulesList">
|
|
<li class="empty" style="border:0; padding:8px;">Noch keine Regeln — König ziehen!</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>📊 Statistik</h2>
|
|
<div class="stats">
|
|
<div class="stat"><div class="num" id="statCards">0</div><div class="label">Karten</div></div>
|
|
<div class="stat"><div class="num" id="statDrinks">0</div><div class="label">Schlucke</div></div>
|
|
<div class="stat"><div class="num" id="statRules">0</div><div class="label">Regeln</div></div>
|
|
<div class="stat"><div class="num" id="statBroken">0</div><div class="label">Verstöße</div></div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
<p class="footer-note">Spiel wird automatisch in localStorage gespeichert · Nur für den JGA — bleibt unter uns 🤫</p>
|
|
</div>
|
|
|
|
<!-- Modal: Spieler-Auswahl für "Gib an einen Spieler"-Aktionen -->
|
|
<div class="modal-overlay" id="modalOverlay">
|
|
<div class="modal">
|
|
<h3 id="modalTitle">Wähle einen Spieler</h3>
|
|
<div class="player-list" id="modalPlayers"></div>
|
|
<div class="modal-actions">
|
|
<button class="btn btn-secondary" id="modalCancel">Abbrechen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
/* ===== Theme Toggle ===== */
|
|
const themeToggle = document.getElementById('themeToggle');
|
|
function setTheme(mode) {
|
|
document.documentElement.setAttribute('data-theme', mode);
|
|
localStorage.setItem('jga-theme', mode);
|
|
themeToggle.textContent = mode === 'dark' ? '🌙' : '☀️';
|
|
}
|
|
themeToggle.addEventListener('click', () => {
|
|
const cur = document.documentElement.getAttribute('data-theme');
|
|
setTheme(cur === 'dark' ? 'light' : 'dark');
|
|
});
|
|
// Init button icon
|
|
setTheme(localStorage.getItem('jga-theme') || 'dark');
|
|
|
|
/* ===== Daten ===== */
|
|
const SUITS = [
|
|
{ symbol: '♠', name: 'Pik', color: 'black' },
|
|
{ symbol: '♥', name: 'Herz', color: 'red' },
|
|
{ symbol: '♦', name: 'Karo', color: 'red' },
|
|
{ symbol: '♣', name: 'Kreuz', color: 'black' }
|
|
];
|
|
const RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
|
|
|
|
const AVATARS = ['😎','🤠','🥳','🤩','😈','🧔','👨','👩','🧑','🦸','🥷','🤡','👻','🎅'];
|
|
|
|
/* ===== Spiel-Konfigurationen ===== */
|
|
const GAMES = {
|
|
kings: {
|
|
title: '👑 Kings',
|
|
desc: 'Ziehe Karten, führe Aktionen aus. Wer alle 4 Könige zieht, trinkt den König-Becher leer.',
|
|
cardActions: (rank, suit) => {
|
|
const base = {
|
|
'2': { who: 'self', text: 'Gib 2 Schluck an einen Spieler', count: 2, target: 'pick' },
|
|
'3': { who: 'self', text: 'Trinke 3 Schluck', count: 3, target: 'self' },
|
|
'4': { who: 'last', text: 'Letzter zum Boden-Berühren trinkt 4', count: 4, target: 'game' },
|
|
'5': { who: 'all', text: 'Kategorie wählen — wer nicht antwortet, trinkt', count: 1, target: 'category' },
|
|
'6': { who: 'last', text: 'Daumen auf den Tisch — Letzter trinkt 6', count: 6, target: 'game' },
|
|
'7': { who: 'all', text: 'Höchste Hand zeigen — Verlierer trinkt 7', count: 7, target: 'game' },
|
|
'8': { who: 'self', text: 'Wähle einen Trink-Partner — ab jetzt immer mit', count: 0, target: 'partner' },
|
|
'9': { who: 'self', text: 'Trink-Sprache wählen — alle reden 1 Min so', count: 0, target: 'language' },
|
|
'10': { who: 'self', text: 'Geschichte erzählen — wer stockt, trinkt 10', count: 10, target: 'self' },
|
|
'J': { who: 'self', text: 'Mach ein Tier-Geräusch — Gruppe entscheidet', count: 0, target: 'task' },
|
|
'Q': { who: 'self', text: 'Quizmaster — falsche Antwort = 5 Schluck', count: 0, target: 'quizmaster' },
|
|
'K': { who: 'self', text: '👑 Erfinde eine Regel (bis zum nächsten König)', count: 0, target: 'rule' },
|
|
'A': { who: 'all', text: '💧 Wasserfall — alle trinken gleichzeitig', count: 999, target: 'waterfall' }
|
|
};
|
|
return base[rank];
|
|
},
|
|
useKingMeter: true
|
|
},
|
|
ride: {
|
|
title: '🚌 Ride the Bus',
|
|
desc: '4 Runden, je mehr Karten du ziehst desto mehr riskierst du. Verlierer trinkt.',
|
|
cardActions: () => ({ who: 'self', text: 'Niedrigste Karte verliert — trinkt Schlucke', count: 1, target: 'round' }),
|
|
useKingMeter: false
|
|
},
|
|
ring: {
|
|
title: '🔥 Ring of Fire',
|
|
desc: '52 Karten im Kreis um den König-Becher. Jede Karte hat eine Regel.',
|
|
cardActions: (rank, suit) => {
|
|
const r = rank;
|
|
const c = suit.color;
|
|
if (c === 'red') {
|
|
if (r === 'A') return { who: 'self', text: 'Trinke 1 Schluck', count: 1 };
|
|
if (r === 'K') return { who: 'self', text: '👑 König-Becher füllen!', count: 0, target: 'king-fill' };
|
|
if (r === 'Q') return { who: 'all', text: 'Quizmaster — wer falsch antwortet trinkt', count: 0, target: 'quizmaster' };
|
|
if (r === 'J') return { who: 'self', text: 'Mach eine Regel auf', count: 0, target: 'rule' };
|
|
// Zahlenkarten
|
|
const n = parseInt(r);
|
|
return { who: 'self', text: `Trinke ${n} Schluck${n>1?'e':''}`, count: n };
|
|
} else {
|
|
// Schwarz
|
|
const map = {
|
|
'A': { who: 'across', text: 'Gegenüber trinkt 1', count: 1, target: 'across' },
|
|
'2': { who: 'self', text: 'Du trinkst 2', count: 2 },
|
|
'3': { who: 'right', text: 'Rechts trinkt 3', count: 3, target: 'right' },
|
|
'4': { who: 'left', text: 'Links trinkt 4', count: 4, target: 'left' },
|
|
'5': { who: 'all', text: 'Wasserfall — Reihe ums Trinken', count: 999, target: 'waterfall' },
|
|
'6': { who: 'last', text: 'Daumen auf Tisch — Letzter trinkt 6', count: 6, target: 'game' },
|
|
'7': { who: 'all', text: 'Wer zuletzt trinkt, trinkt nochmal 7', count: 7, target: 'reverse-game' },
|
|
'8': { who: 'self', text: 'Mach ein Foto von der Runde', count: 0, target: 'photo' },
|
|
'9': { who: 'all', text: 'Sozial — alle trinken mit', count: 1 },
|
|
'10':{ who: 'self', text: 'Quizmaster', count: 0, target: 'quizmaster' },
|
|
'J': { who: 'self', text: 'Erfand eine Regel', count: 0, target: 'rule' },
|
|
'Q': { who: 'self', text: 'Becher füllen', count: 0, target: 'king-fill' },
|
|
'K': { who: 'all', text: 'Trink aus dem König-Becher', count: 0, target: 'king-drink' }
|
|
};
|
|
return map[r];
|
|
}
|
|
},
|
|
useKingMeter: true
|
|
},
|
|
never: {
|
|
title: '🚫 Ich hab noch nie',
|
|
desc: 'Eine Aussage, alle die es schon gemacht haben trinken.',
|
|
cardActions: (rank, suit) => {
|
|
// Wir nutzen die Karten als Zufalls-Picker aus Datenbank
|
|
const prompts = [
|
|
'noch nie einen Stripclub besucht',
|
|
'noch nie im Casino gespielt',
|
|
'noch nie bei Rot über die Ampel',
|
|
'noch ne beim Schwimmen gepinkelt',
|
|
'noch nie in der Öffentlichkeit gesungen',
|
|
'noch nie beim Film im Kino eingeschlafen',
|
|
'noch nie fremdes Essen aus dem Kühlschrank',
|
|
'noch nie eine 1 auf dem Zeugnis',
|
|
'noch nie einen Strafzettel bekommen',
|
|
'noch ne im Bus die Notbremse gezogen',
|
|
'noch nie beim Date die Wohnung verwüstet',
|
|
'noch nie den falschen Namen gerufen',
|
|
'noch nie nach Alkohol im Bett eingeschlafen',
|
|
'noch nie beim Joggen das Handy verloren',
|
|
'noch nie beim Zahnarzt geweint',
|
|
'noch nie im Stau auf der Autobahn gepinkelt',
|
|
'noch ne einen Liebesbrief geschrieben',
|
|
'noch nie einen Joint geraucht',
|
|
'noch nie alleine im Restaurant gegessen',
|
|
'noch ne den Zug verpasst',
|
|
'noch nie ein Haustier zum Tierarzt',
|
|
'noch nie beim Kochen die Küche abgefackelt',
|
|
'noch nie auf einer Beerdigung gelacht',
|
|
'noch ne beim Wandern verlaufen',
|
|
'noch nie bei der Arbeit geweint'
|
|
];
|
|
const idx = parseInt(rank === 'A' ? 1 : rank === 'J' ? 11 : rank === 'Q' ? 12 : rank === 'K' ? 13 : rank) - 1;
|
|
return { who: 'all', text: `Ich hab noch nie ${prompts[idx % prompts.length]}`, count: 1, target: 'never' };
|
|
},
|
|
useKingMeter: false
|
|
},
|
|
truth: {
|
|
title: '🎯 Wahrheit oder Pflicht',
|
|
desc: 'Wähle Wahrheit (Frage beantworten) oder Pflicht (Aufgabe machen). Verweigerung = Trinken.',
|
|
cardActions: (rank, suit) => {
|
|
const truths = [
|
|
'Was ist dein peinlichstes Erlebnis auf einer Party?',
|
|
'Wer war dein erster Crush?',
|
|
'Was ist das Verrückteste, das du je getrunken hast?',
|
|
'Hast du jemals im Bett etwas gemacht, das du bereut hast?',
|
|
'Was ist dein geheimer Fetisch?',
|
|
'Was war dein dümmster Unfall?',
|
|
'Was war der letzte peinliche Moment?',
|
|
'Hast du jemals einen Falschtext geschickt?',
|
|
'Wer ist dein "Celebrity Free Pass"?',
|
|
'Was ist das Schlimmste, das du je gegessen hast?',
|
|
'Was war dein unangenehmster Arztbesuch?',
|
|
'Was war dein erster Kuss?',
|
|
'Wer war dein schlimmster Date?',
|
|
'Was ist dein geheimer Traum?'
|
|
];
|
|
const dares = [
|
|
'Imitate einen Porn-Star',
|
|
'Schicke deiner Chefin eine alberne SMS',
|
|
'Trinke ein Glas Wasser in 5 Sekunden',
|
|
'Tanze 30 Sekunden ohne Musik',
|
|
'Mach 10 Liegestütze',
|
|
'Lies den letzten Chat-Verlauf laut vor',
|
|
'Mach ein Selfie mit der absurdesten Grimasse',
|
|
'Trinke ein Getränk auf ex',
|
|
'Singe den Refrain deines Lieblingslieds',
|
|
'Laufe 10 Sekunden wie ein Model',
|
|
'Erzähle einen Witz',
|
|
'Mach einen Handstand',
|
|
'Spiele 30 Sekunden Luftgitarre',
|
|
'Iss einen Löffel Senf'
|
|
];
|
|
const idx = parseInt(rank === 'A' ? 1 : rank === 'J' ? 11 : rank === 'Q' ? 12 : rank === 'K' ? 13 : rank) - 1;
|
|
const isTruth = suit.color === 'red';
|
|
return { who: 'self', text: isTruth ? `Wahrheit: ${truths[idx % truths.length]}` : `Pflicht: ${dares[idx % dares.length]}`, count: 0, target: 'truth-dare', prompt: isTruth ? 'truth' : 'dare' };
|
|
},
|
|
useKingMeter: false
|
|
}
|
|
};
|
|
|
|
/* ===== State ===== */
|
|
const state = {
|
|
game: 'kings',
|
|
players: JSON.parse(localStorage.getItem('jga-players-v2') || '[]'),
|
|
rules: JSON.parse(localStorage.getItem('jga-rules-v2') || '[]'),
|
|
history: JSON.parse(localStorage.getItem('jga-history-v2') || '[]'),
|
|
kingCount: parseInt(localStorage.getItem('jga-king-count') || '0'),
|
|
brokenRules: parseInt(localStorage.getItem('jga-broken-rules') || '0')
|
|
};
|
|
|
|
// Default-Spieler wenn leer
|
|
if (state.players.length === 0) {
|
|
state.players = [
|
|
{ name: 'Bräutigam', avatar: '🤵', drinks: 0 },
|
|
{ name: 'Trauzeuge', avatar: '🤠', drinks: 0 }
|
|
];
|
|
savePlayers();
|
|
}
|
|
|
|
/* ===== Helpers ===== */
|
|
function save() {
|
|
localStorage.setItem('jga-players-v2', JSON.stringify(state.players));
|
|
localStorage.setItem('jga-rules-v2', JSON.stringify(state.rules));
|
|
localStorage.setItem('jga-history-v2', JSON.stringify(state.history));
|
|
localStorage.setItem('jga-king-count', state.kingCount.toString());
|
|
localStorage.setItem('jga-broken-rules', state.brokenRules.toString());
|
|
}
|
|
function savePlayers() { localStorage.setItem('jga-players-v2', JSON.stringify(state.players)); }
|
|
function randAvatar() { return AVATARS[Math.floor(Math.random() * AVATARS.length)]; }
|
|
|
|
function buildDeck() {
|
|
const deck = [];
|
|
for (const suit of SUITS) {
|
|
for (const rank of RANKS) {
|
|
deck.push({ rank, suit });
|
|
}
|
|
}
|
|
return deck.sort(() => Math.random() - 0.5);
|
|
}
|
|
|
|
let currentDeck = buildDeck();
|
|
let currentCard = null;
|
|
let currentPlayer = null;
|
|
|
|
/* ===== Render ===== */
|
|
function renderPlayers() {
|
|
const list = document.getElementById('playerList');
|
|
if (state.players.length === 0) {
|
|
list.innerHTML = '<div class="empty">Noch keine Spieler — füge welche hinzu</div>';
|
|
return;
|
|
}
|
|
list.innerHTML = state.players.map((p, i) => `
|
|
<div class="player ${p.drinks >= 10 ? 'drunk' : ''}">
|
|
<span class="avatar">${p.avatar}</span>
|
|
<span class="name">${escapeHtml(p.name)}</span>
|
|
<span class="drinks">${p.drinks}</span>
|
|
<button class="remove" data-idx="${i}" title="Entfernen">✕</button>
|
|
</div>
|
|
`).join('');
|
|
list.querySelectorAll('.remove').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const idx = parseInt(btn.dataset.idx);
|
|
state.players.splice(idx, 1);
|
|
savePlayers();
|
|
renderPlayers();
|
|
renderStats();
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderRules() {
|
|
const list = document.getElementById('rulesList');
|
|
if (state.rules.length === 0) {
|
|
list.innerHTML = '<li class="empty" style="border:0; padding:8px;">Noch keine Regeln — König ziehen!</li>';
|
|
return;
|
|
}
|
|
list.innerHTML = state.rules.map((r, i) => `
|
|
<li>
|
|
<span class="rule-text">
|
|
${escapeHtml(r.text)}
|
|
<div class="rule-meta">von ${escapeHtml(r.author)} · ${r.broken}x gebrochen</div>
|
|
</span>
|
|
<button data-idx="${i}" title="Regel aufheben">✕</button>
|
|
</li>
|
|
`).join('');
|
|
list.querySelectorAll('button').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
state.rules.splice(parseInt(btn.dataset.idx), 1);
|
|
save();
|
|
renderRules();
|
|
renderStats();
|
|
});
|
|
});
|
|
}
|
|
|
|
function renderStats() {
|
|
const totalDrinks = state.players.reduce((sum, p) => sum + p.drinks, 0);
|
|
document.getElementById('statCards').textContent = state.history.length;
|
|
document.getElementById('statDrinks').textContent = totalDrinks;
|
|
document.getElementById('statRules').textContent = state.rules.length;
|
|
document.getElementById('statBroken').textContent = state.brokenRules;
|
|
}
|
|
|
|
function renderHistory() {
|
|
const hist = document.getElementById('history');
|
|
if (state.history.length === 0) {
|
|
hist.innerHTML = '<div class="empty">Noch keine Karten gezogen</div>';
|
|
return;
|
|
}
|
|
hist.innerHTML = state.history.slice().reverse().slice(0, 30).map(h => {
|
|
const colorClass = h.suitColor === 'red' ? 'red' : 'black';
|
|
return `
|
|
<div class="history-item ${colorClass}">
|
|
<span class="mini-card">${h.suitSymbol}${h.rank}</span>
|
|
<span>${escapeHtml(h.action)}</span>
|
|
<span class="who-text">${escapeHtml(h.who)}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function renderKingMeter() {
|
|
const cfg = GAMES[state.game];
|
|
const meter = document.getElementById('kingMeter');
|
|
if (cfg.useKingMeter) {
|
|
meter.style.display = 'block';
|
|
document.getElementById('kingCount').textContent = `${state.kingCount} / 4`;
|
|
document.getElementById('kingFill').style.width = (state.kingCount / 4 * 100) + '%';
|
|
} else {
|
|
meter.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
return String(s).replace(/[&<>"']/g, c => ({
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
}[c]));
|
|
}
|
|
|
|
function renderGameInfo() {
|
|
const cfg = GAMES[state.game];
|
|
document.getElementById('gameTitle').textContent = cfg.title;
|
|
document.getElementById('gameDescription').textContent = cfg.desc;
|
|
renderKingMeter();
|
|
}
|
|
|
|
/* ===== Karten-Logik ===== */
|
|
function drawCard() {
|
|
if (currentDeck.length === 0) {
|
|
currentDeck = buildDeck();
|
|
alert('Deck neu gemischt!');
|
|
}
|
|
currentCard = currentDeck.pop();
|
|
document.getElementById('cardSuit').textContent = currentCard.suit.symbol;
|
|
document.getElementById('cardRank').textContent = currentCard.rank;
|
|
const cardFront = document.getElementById('cardFront');
|
|
cardFront.classList.remove('red', 'black');
|
|
cardFront.classList.add(currentCard.suit.color);
|
|
|
|
// Action bestimmen
|
|
const cfg = GAMES[state.game];
|
|
const action = cfg.cardActions(currentCard.rank, currentCard.suit);
|
|
document.getElementById('cardAction').textContent = action.text;
|
|
document.getElementById('cardWho').textContent = action.who === 'self' ? 'DU' : (action.who === 'all' ? 'ALLE' : 'GRUPPE');
|
|
|
|
// Karten flippen
|
|
const wrap = document.getElementById('cardWrap');
|
|
wrap.classList.remove('flipped');
|
|
// Force reflow
|
|
void wrap.offsetWidth;
|
|
wrap.classList.add('flipped');
|
|
|
|
// Speichern in History
|
|
currentPlayer = state.players[Math.floor(Math.random() * state.players.length)];
|
|
state.history.push({
|
|
rank: currentCard.rank,
|
|
suitSymbol: currentCard.suit.symbol,
|
|
suitColor: currentCard.suit.color,
|
|
action: action.text,
|
|
who: currentPlayer ? currentPlayer.name : '—'
|
|
});
|
|
|
|
// Trink-Count auf Spieler
|
|
if (action.count > 0 && currentPlayer && action.who !== 'all') {
|
|
currentPlayer.drinks += action.count;
|
|
} else if (action.count > 0 && action.who === 'all') {
|
|
state.players.forEach(p => p.drinks += action.count);
|
|
}
|
|
|
|
// Spezial-Aktionen
|
|
if (action.target === 'rule') {
|
|
const ruleText = prompt('Welche Regel gilt bis zum nächsten König?');
|
|
if (ruleText && ruleText.trim()) {
|
|
state.rules.push({
|
|
text: ruleText.trim(),
|
|
author: currentPlayer ? currentPlayer.name : '—',
|
|
created: Date.now(),
|
|
broken: 0
|
|
});
|
|
}
|
|
}
|
|
if (action.target === 'pick') {
|
|
showPlayerModal('Wähle einen Spieler — der bekommt ' + action.count + ' Schluck(e)', (picked) => {
|
|
picked.drinks += action.count;
|
|
savePlayers();
|
|
renderPlayers();
|
|
});
|
|
}
|
|
if (action.target === 'partner') {
|
|
showPlayerModal('Wähle deinen Trink-Partner', (picked) => {
|
|
alert(`${picked.name} ist jetzt dein Trink-Partner!`);
|
|
});
|
|
}
|
|
|
|
// König-Meter (Kings-Spiel)
|
|
if (cfg.useKingMeter) {
|
|
if (currentCard.rank === 'K') {
|
|
state.kingCount++;
|
|
if (state.kingCount === 4 && currentPlayer) {
|
|
currentPlayer.drinks += 20; // König-Becher
|
|
alert(`👑👑👑👑 ${currentPlayer.name} hat den 4. König gezogen — König-Becher leertrinken! +20 Schlucke`);
|
|
state.kingCount = 0;
|
|
}
|
|
}
|
|
save();
|
|
renderKingMeter();
|
|
}
|
|
|
|
save();
|
|
renderPlayers();
|
|
renderHistory();
|
|
renderStats();
|
|
document.getElementById('drawStatus').textContent = `Karten im Deck: ${currentDeck.length}`;
|
|
}
|
|
|
|
/* ===== Modal ===== */
|
|
function showPlayerModal(title, callback) {
|
|
const overlay = document.getElementById('modalOverlay');
|
|
document.getElementById('modalTitle').textContent = title;
|
|
const list = document.getElementById('modalPlayers');
|
|
list.innerHTML = state.players.map((p, i) => `
|
|
<div class="player" style="cursor:pointer;" data-idx="${i}">
|
|
<span class="avatar">${p.avatar}</span>
|
|
<span class="name">${escapeHtml(p.name)}</span>
|
|
<span class="drinks">${p.drinks}</span>
|
|
</div>
|
|
`).join('');
|
|
list.querySelectorAll('.player').forEach(el => {
|
|
el.addEventListener('click', () => {
|
|
const idx = parseInt(el.dataset.idx);
|
|
callback(state.players[idx]);
|
|
overlay.classList.remove('open');
|
|
});
|
|
});
|
|
overlay.classList.add('open');
|
|
}
|
|
document.getElementById('modalCancel').addEventListener('click', () => {
|
|
document.getElementById('modalOverlay').classList.remove('open');
|
|
});
|
|
|
|
/* ===== Events ===== */
|
|
document.querySelectorAll('.game-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.game-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
state.game = btn.dataset.game;
|
|
currentDeck = buildDeck();
|
|
state.kingCount = 0;
|
|
save();
|
|
renderGameInfo();
|
|
document.getElementById('cardWrap').classList.remove('flipped');
|
|
document.getElementById('drawStatus').textContent = `Karten im Deck: ${currentDeck.length}`;
|
|
});
|
|
});
|
|
|
|
document.getElementById('drawBtn').addEventListener('click', drawCard);
|
|
document.getElementById('cardWrap').addEventListener('click', drawCard);
|
|
|
|
document.getElementById('resetBtn').addEventListener('click', () => {
|
|
if (confirm('Wirklich neues Spiel starten? Spieler bleiben, Stats werden zurückgesetzt.')) {
|
|
currentDeck = buildDeck();
|
|
state.history = [];
|
|
state.kingCount = 0;
|
|
state.brokenRules = 0;
|
|
state.players.forEach(p => p.drinks = 0);
|
|
document.getElementById('cardWrap').classList.remove('flipped');
|
|
save();
|
|
renderPlayers();
|
|
renderHistory();
|
|
renderStats();
|
|
renderKingMeter();
|
|
document.getElementById('drawStatus').textContent = `Karten im Deck: ${currentDeck.length}`;
|
|
}
|
|
});
|
|
|
|
document.getElementById('addPlayerBtn').addEventListener('click', addPlayer);
|
|
document.getElementById('playerInput').addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter') addPlayer();
|
|
});
|
|
function addPlayer() {
|
|
const input = document.getElementById('playerInput');
|
|
const name = input.value.trim();
|
|
if (!name) return;
|
|
state.players.push({ name, avatar: randAvatar(), drinks: 0 });
|
|
input.value = '';
|
|
savePlayers();
|
|
renderPlayers();
|
|
renderStats();
|
|
}
|
|
|
|
/* ===== Init ===== */
|
|
renderPlayers();
|
|
renderRules();
|
|
renderStats();
|
|
renderHistory();
|
|
renderGameInfo();
|
|
document.getElementById('drawStatus').textContent = `Karten im Deck: ${currentDeck.length}`;
|
|
|
|
/* ===== Service Worker ===== */
|
|
// PWA Service Worker — Network-First Strategie (Updates kommen sofort durch)
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('./sw.js')
|
|
.then(reg => {
|
|
console.log('[PWA] SW registered:', reg.scope);
|
|
// Update-Hook: wenn ein neuer SW installiert wurde, kurz reloaden
|
|
reg.addEventListener('updatefound', () => {
|
|
const newSw = reg.installing;
|
|
if (!newSw) return;
|
|
newSw.addEventListener('statechange', () => {
|
|
if (newSw.state === 'activated' && navigator.serviceWorker.controller) {
|
|
console.log('[PWA] Neue Version aktiviert — lade automatisch neu');
|
|
window.location.reload();
|
|
}
|
|
});
|
|
});
|
|
})
|
|
.catch(err => console.warn('[PWA] SW registration failed:', err));
|
|
});
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html> |