Files
JGA-2026/countdown.html
Hermes AI Bot 4e1691ac37 fix(cache): auto-reload bei SW-Update statt confirm-Prompt
Bei neuem Service Worker wird die Seite automatisch neu geladen.
Kein Popup mehr für die User.
2026-06-27 13:08:41 +00:00

423 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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>JGA Prag 2026 — Countdown</title>
<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>">
<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() {
var saved = localStorage.getItem('jga-theme');
document.documentElement.setAttribute('data-theme', saved || 'dark');
})();
</script>
<style>
:root, [data-theme="dark"] {
--bg: #1c1c1f; --card-bg: #26262a; --card-border: #353539;
--text: #e8e8ea; --text-strong: #fff; --text-muted: #999;
--accent: #3b82f6; --accent-text: #60a5fa;
--gradient-start: #1c1c1f; --gradient-end: #2a2a30;
}
[data-theme="light"] {
--bg: #f5f5f7; --card-bg: #ffffff; --card-border: #e0e0e3;
--text: #1a1a1a; --text-strong: #000; --text-muted: #666;
--accent: #2563eb; --accent-text: #2563eb;
--gradient-start: #ffffff; --gradient-end: #e0e7ff;
}
* { 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;
display: flex; align-items: center; justify-content: center;
}
.container {
text-align: center;
max-width: 700px;
padding: 40px 24px;
width: 100%;
}
.emoji-big {
font-size: 100px;
line-height: 1;
margin-bottom: 16px;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-15px); }
}
h1 {
font-size: 38px;
margin: 0 0 8px 0;
color: var(--text-strong);
font-weight: 700;
}
.subtitle {
color: var(--text-muted);
font-size: 18px;
margin: 0 0 40px 0;
}
.countdown-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 32px;
}
.countdown-cell {
background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
border: 1px solid var(--card-border);
border-radius: 16px;
padding: 24px 12px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.countdown-num {
font-size: 64px;
font-weight: 800;
color: var(--accent-text);
line-height: 1;
font-variant-numeric: tabular-nums;
}
.countdown-label {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1.5px;
margin-top: 8px;
font-weight: 600;
}
.countdown-message {
font-size: 22px;
color: var(--text);
margin: 24px 0;
line-height: 1.5;
}
.countdown-message.celebrating {
font-size: 32px;
color: var(--accent-text);
font-weight: 700;
}
.progress-bar {
background: var(--card-border);
height: 12px;
border-radius: 6px;
overflow: hidden;
margin: 32px 0 16px 0;
}
.progress-bar .fill {
height: 100%;
background: linear-gradient(90deg, var(--accent) 0%, #06b6d4 100%);
width: 0%;
transition: width 1s ease;
border-radius: 6px;
}
.progress-text {
font-size: 14px;
color: var(--text-muted);
margin-top: 8px;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
margin-top: 32px;
}
.info-item {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-left: 3px solid var(--accent);
padding: 12px 16px;
border-radius: 8px;
text-align: left;
}
.info-item .label {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
}
.info-item .value {
font-size: 15px;
color: var(--text-strong);
font-weight: 600;
}
.share-btn {
display: inline-block;
padding: 12px 28px;
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
margin-top: 24px;
text-decoration: none;
transition: transform 0.2s, background 0.2s;
}
.share-btn:hover {
transform: translateY(-2px);
}
.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: 0 2px 8px rgba(0,0,0,0.15);
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);
}
.back-link {
display: inline-block;
margin-bottom: 16px;
color: var(--accent-text);
text-decoration: none;
font-size: 14px;
}
.back-link:hover { text-decoration: underline; }
@media (max-width: 480px) {
.countdown-num { font-size: 40px; }
h1 { font-size: 28px; }
}
</style>
</head>
<body>
<a href="index.html" class="home-btn" title="Zurück zur Startseite">🏠 Startseite</a>
<button class="theme-toggle" id="theme-toggle" aria-label="Theme wechseln" title="Light/Dark Mode">🌙</button>
<div class="container">
<a href="index.html" class="back-link">← Zurück zur Übersicht</a>
<div class="emoji-big" id="emoji">🥳</div>
<h1 id="title">JGA Prag 2026</h1>
<p class="subtitle" id="subtitle">Countdown bis zum Junggesellenabschied</p>
<div class="countdown-grid">
<div class="countdown-cell">
<div class="countdown-num" id="days">--</div>
<div class="countdown-label">Tage</div>
</div>
<div class="countdown-cell">
<div class="countdown-num" id="hours">--</div>
<div class="countdown-label">Stunden</div>
</div>
<div class="countdown-cell">
<div class="countdown-num" id="minutes">--</div>
<div class="countdown-label">Minuten</div>
</div>
<div class="countdown-cell">
<div class="countdown-num" id="seconds">--</div>
<div class="countdown-label">Sekunden</div>
</div>
</div>
<div class="countdown-message" id="message">Noch so lange bis zur wildesten Party des Jahres!</div>
<div class="progress-bar"><div class="fill" id="progress-fill"></div></div>
<div class="progress-text" id="progress-text">Vorbereitung: 0%</div>
<div class="info-grid">
<div class="info-item">
<div class="label">📅 Datum</div>
<div class="value">25.26. Juli 2026</div>
</div>
<div class="info-item">
<div class="label">👥 Gruppe</div>
<div class="value">8 Personen</div>
</div>
<div class="info-item">
<div class="label">🏨 Hotel</div>
<div class="value">Hotel Uno Prague</div>
</div>
<div class="info-item">
<div class="label">🎯 Aktivitäten</div>
<div class="value">11 Locations</div>
</div>
</div>
<button class="share-btn" id="share-btn">📤 Teilen</button>
</div>
<script>
// JGA-Daten: 25. Juli 2026, Start 12:00 Uhr Berlin (Annahme)
const JGA_START = new Date('2026-07-25T12:00:00+02:00').getTime();
// Planungsstart: heute minus 30 Tage = ca. 1. Juni 2026
const PLANNING_START = new Date('2026-06-01T00:00:00+02:00').getTime();
function update() {
const now = Date.now();
const diff = JGA_START - now;
if (diff <= 0) {
// JGA läuft oder vorbei
document.getElementById('days').textContent = '🎉';
document.getElementById('hours').textContent = '🎉';
document.getElementById('minutes').textContent = '🎉';
document.getElementById('seconds').textContent = '🎉';
document.getElementById('message').classList.add('celebrating');
const jgaEnd = new Date('2026-07-26T23:59:00+02:00').getTime();
if (now > jgaEnd) {
document.getElementById('message').innerHTML = '🥲 Der JGA ist vorbei — was für ein Wochenende!';
document.getElementById('emoji').textContent = '🥲';
document.getElementById('title').textContent = 'Erinnerungen';
} else {
document.getElementById('message').innerHTML = '🎉 Der JGA LÄUFT! Genießt jeden Moment! 🎉';
document.getElementById('emoji').textContent = '🥳';
document.getElementById('title').textContent = 'Es ist soweit!';
}
document.getElementById('progress-fill').style.width = '100%';
document.getElementById('progress-text').textContent = 'Planung abgeschlossen';
return;
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
document.getElementById('days').textContent = days;
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
// Progress Bar
const total = JGA_START - PLANNING_START;
const elapsed = now - PLANNING_START;
const pct = Math.min(100, Math.max(0, (elapsed / total) * 100));
document.getElementById('progress-fill').style.width = pct.toFixed(1) + '%';
document.getElementById('progress-text').textContent = `Vorbereitung: ${pct.toFixed(1)}%`;
// Kontextabhängige Nachricht
let msg = 'Noch so lange bis zur wildesten Party des Jahres!';
let emoji = '🥳';
if (days === 0) {
msg = '🔥 Heute ist der Tag! Auf geht\'s nach Prag!';
emoji = '🔥';
} else if (days === 1) {
msg = '😱 MORGEN ist es soweit! Letzte Vorbereitungen!';
emoji = '😱';
} else if (days <= 7) {
msg = '⚡ Nur noch ' + days + ' Tage! Packlisten checken!';
emoji = '⚡';
} else if (days <= 30) {
msg = '📦 Bald geht\'s los! Restliche Buchungen klären.';
emoji = '📦';
} else if (days <= 90) {
msg = '🗓️ ' + days + ' Tage — genug Zeit für Feinschliff.';
emoji = '🗓️';
}
document.getElementById('message').textContent = msg;
document.getElementById('emoji').textContent = emoji;
}
update();
setInterval(update, 1000);
// Theme-Toggle
var themeBtn = document.getElementById('theme-toggle');
function syncThemeIcon() {
var t = document.documentElement.getAttribute('data-theme') || 'dark';
themeBtn.textContent = t === 'dark' ? '☀️' : '🌙';
themeBtn.title = t === 'dark' ? 'Light Mode aktivieren' : 'Dark Mode aktivieren';
}
syncThemeIcon();
themeBtn.addEventListener('click', function() {
var t = document.documentElement.getAttribute('data-theme') || 'dark';
var next = t === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
try { localStorage.setItem('jga-theme', next); } catch (e) {}
syncThemeIcon();
});
// Share-Button
document.getElementById('share-btn').addEventListener('click', async function() {
var days = document.getElementById('days').textContent;
var shareText = '🥳 JGA Prag 2026 — noch ' + days + ' Tage! 25.26. Juli 2026 · jga.orfel.de';
if (navigator.share) {
try {
await navigator.share({ title: 'JGA Prag 2026', text: shareText, url: window.location.href });
} catch (e) { /* user cancelled */ }
} else if (navigator.clipboard) {
try {
await navigator.clipboard.writeText(shareText);
this.textContent = '✅ Kopiert!';
setTimeout(() => { this.textContent = '📤 Teilen'; }, 2000);
} catch (e) { /* clipboard failed */ }
} else {
prompt('Zum Kopieren:', shareText);
}
});
// PWA Service Worker — Network-First Strategie (Updates kommen sofort durch)
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('./sw.js')
.then(function(reg) {
console.log('[PWA] SW registered:', reg.scope);
reg.addEventListener('updatefound', function() {
var newSw = reg.installing;
if (!newSw) return;
newSw.addEventListener('statechange', function() {
if (newSw.state === 'activated' && navigator.serviceWorker.controller) {
console.log('[PWA] Neue Version aktiviert — lade automatisch neu');
window.location.reload();
}
});
});
})
.catch(function(err) { console.warn('[PWA] SW failed:', err); });
});
}
</script>
</body>
</html>