Files
JGA-2026/packliste.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

408 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 Packliste</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-border: #353539;
--text: #e8e8ea;
--text-muted: #999;
--text-strong: #fff;
--accent: #3b82f6;
--accent-text: #60a5fa;
--progress-bg: #2a2a2d;
}
[data-theme="light"] {
--bg: #f5f5f7;
--card-bg: #ffffff;
--card-border: #e0e0e3;
--text: #1a1a1a;
--text-muted: #666;
--text-strong: #000;
--accent: #2563eb;
--accent-text: #2563eb;
--progress-bg: #e0e0e3;
}
* { 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: 900px; margin: 0 auto; padding: 40px 24px; }
h1 {
font-size: 38px;
margin: 0 0 8px 0;
color: var(--text-strong);
font-weight: 700;
}
.subtitle { color: var(--text-muted); margin-bottom: 32px; font-size: 16px; }
.back-link {
display: inline-block;
margin-bottom: 24px;
color: var(--accent-text);
text-decoration: none;
font-size: 14px;
}
.back-link:hover { text-decoration: underline; }
/* Theme-Toggle Button */
.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);
}
.category {
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 20px 24px;
margin-bottom: 16px;
}
.category h2 {
margin: 0 0 16px 0;
font-size: 20px;
color: var(--text-strong);
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.category h2 .icon {
font-size: 28px;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", emoji;
}
.checklist { list-style: none; padding: 0; margin: 0; }
.checklist li {
padding: 8px 0;
border-bottom: 1px solid var(--card-border);
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 15px;
color: var(--text);
}
.checklist li:last-child { border-bottom: none; }
.checklist input[type="checkbox"] {
appearance: none;
width: 18px;
height: 18px;
border: 2px solid var(--accent);
border-radius: 4px;
flex-shrink: 0;
margin-top: 2px;
cursor: pointer;
position: relative;
background: transparent;
}
.checklist input[type="checkbox"]:checked {
background: var(--accent);
border-color: var(--accent);
}
.checklist input[type="checkbox"]:checked::after {
content: "✓";
color: white;
position: absolute;
left: 2px;
top: -3px;
font-size: 14px;
font-weight: bold;
}
.checklist li.checked span {
text-decoration: line-through;
color: var(--text-muted);
}
.checklist li small {
display: block;
color: var(--text-muted);
font-size: 12px;
margin-top: 2px;
}
.progress-bar {
background: var(--progress-bg);
height: 8px;
border-radius: 4px;
overflow: hidden;
margin: 24px 0;
}
.progress-bar .fill {
height: 100%;
background: var(--accent);
width: 0%;
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
color: var(--text-muted);
font-size: 13px;
margin-bottom: 24px;
}
@media print {
body { background: white; color: black; }
.category { background: white; border: 1px solid #ccc; }
.checklist input[type="checkbox"] {
border-color: #333;
}
button { display: none !important; }
}
</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>
<h1>🎒 JGA Prag Packliste</h1>
<p class="subtitle">Für 8 Personen · 25.26. Juli 2026 · Wochenende</p>
<div class="progress-text" id="progress-text">0 von 0 erledigt</div>
<div class="progress-bar"><div class="fill" id="progress-fill"></div></div>
<div style="display:flex;gap:12px;justify-content:center;margin-bottom:24px;flex-wrap:wrap;">
<button type="button" onclick="window.print()" style="padding:10px 20px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-weight:500;cursor:pointer;font-size:14px;">🖨️ Als PDF drucken</button>
<button type="button" id="reset-btn" style="padding:10px 20px;background:transparent;color:#aaa;border:1px solid #353539;border-radius:6px;font-weight:500;cursor:pointer;font-size:14px;">↻ Häkchen zurücksetzen</button>
</div>
<div class="category">
<h2><span class="icon">📄</span>Dokumente & Geld</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Personalausweis oder Reisepass<small>(Personalausweis reicht innerhalb EU)</small></span></li>
<li><input type="checkbox"><span>Krankenversicherungskarte</span></li>
<li><input type="checkbox"><span>Bargeld in CZK (~2.000 CZK pro Person für Trinkgeld & kleine Ausgaben)</span></li>
<li><input type="checkbox"><span>EC-Karte / Kreditkarte<small>(Visa/Mastercard fast überall akzeptiert)</small></span></li>
<li><input type="checkbox"><span>Hotel-Buchungsbestätigung (auf dem Handy)</span></li>
<li><input type="checkbox"><span>Bordkarte / Zugticket Hinfahrt + Rückfahrt</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">👕</span>Kleidung</h2>
<ul class="checklist">
<li><input type="checkbox"><span>T-Shirts / Hemden (23 Stück)</span></li>
<li><input type="checkbox"><span>Hose / Shorts</span></li>
<li><input type="checkbox"><span>JGA-Shirt / Outfit<small>(falls ihr eines habt)</small></span></li>
<li><input type="checkbox"><span>Leichte Jacke / Hoodie<small>(Abende, Geister-Tour)</small></span></li>
<li><input type="checkbox"><span>Unterwäsche + Socken (2 Tage)</span></li>
<li><input type="checkbox"><span>Bequeme Schuhe fürs Laufen</span></li>
<li><input type="checkbox"><span>Schickere Schuhe für Clubs/Restaurants</span></li>
<li><input type="checkbox"><span>Schwimmzeug (falls Beer Spa)</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">🧴</span>Pflege & Hygiene</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Zahnbürste + Zahnpasta</span></li>
<li><input type="checkbox"><span>Duschgel / Shampoo</span></li>
<li><input type="checkbox"><span>Deo</span></li>
<li><input type="checkbox"><span>Sonnencreme</span></li>
<li><input type="checkbox"><span>Handtuch (klein, für unterwegs)</span></li>
<li><input type="checkbox"><span>Kopfschmerztabletten</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">📱</span>Technik</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Handy + Ladekabel</span></li>
<li><input type="checkbox"><span>Powerbank<small>(Tag wird lang!)</small></span></li>
<li><input type="checkbox"><span>Kopfhörer</span></li>
<li><input type="checkbox"><span>EU-Ladeadapter<small>(Prag nutzt Typ E, wie in DE/AT)</small></span></li>
<li><input type="checkbox"><span>Kamera (optional)</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">🚇</span>Prag-spezifisch</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Google Translate (Offline-Pack Tschechisch installieren!)</span></li>
<li><input type="checkbox"><span>Bolt-App installieren<small>(Uber gibt's nicht mehr in Prag!)</small></span></li>
<li><input type="checkbox"><span>Google Maps Offline-Karte Prag laden</span></li>
<li><input type="checkbox"><span>PID Lítačka App (für Tram-Tickets)<small>(oder Bargeld im Tram)</small></span></li>
<li><input type="checkbox"><span>Notfall-Nummer: 112 (EU-Notruf)</span></li>
<li><input type="checkbox"><span>Kleiner tschechischer Sprachführer (Pivo, Děkuji, Účet prosím)</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">🎯</span>JGA-Spezial (nach Aktivität)</h2>
<ul class="checklist">
<li><input type="checkbox"><span>🎯 Für Schießstand: gültiger Ausweis (wird kontrolliert)</span></li>
<li><input type="checkbox"><span>⛳ Für Minigolf: nichts spezielles</span></li>
<li><input type="checkbox"><span>🔫 Für Lasertag: nichts spezielles</span></li>
<li><input type="checkbox"><span>🎨 Für Paintball: alte Klamotten (es wird schmutzig!)</span></li>
<li><input type="checkbox"><span>🏎️ Für Kart: lange Hose + feste Schuhe</span></li>
<li><input type="checkbox"><span>🔐 Für Escape Room: pünktlich da sein (sonst verpasst ihr eure Startzeit)</span></li>
<li><input type="checkbox"><span>👻 Für Geister-Tour: bequeme Schuhe, 1,75 h zu Fuß</span></li>
<li><input type="checkbox"><span>⛵ Für Beer Boat: nichts spezielles (Bier ist dabei 🍺)</span></li>
<li><input type="checkbox"><span>🔍 Für Enigma: Smartphone voll geladen</span></li>
</ul>
</div>
<div class="category">
<h2><span class="icon">🍺</span>Verpflegung</h2>
<ul class="checklist">
<li><input type="checkbox"><span>Wasserflasche (für unterwegs)</span></li>
<li><input type="checkbox"><span>Knabberzeug für den Zug</span></li>
<li><input type="checkbox"><span>Kaugummis / Pfefferminz<small>(für zwischendurch)</small></span></li>
</ul>
</div>
</div>
<script>
const STORAGE_KEY = 'jga-packliste-checked';
let checked = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
const checkboxes = document.querySelectorAll('.checklist input[type="checkbox"]');
const progressText = document.getElementById('progress-text');
const progressFill = document.getElementById('progress-fill');
checkboxes.forEach((cb, i) => {
if (checked[i]) {
cb.checked = true;
cb.parentElement.classList.add('checked');
}
cb.dataset.index = i;
});
function updateProgress() {
const total = checkboxes.length;
const done = Array.from(checkboxes).filter(cb => cb.checked).length;
const pct = (done / total * 100).toFixed(0);
progressFill.style.width = pct + '%';
progressText.textContent = `${done} von ${total} erledigt (${pct}%)`;
checked = Array.from(checkboxes).map(cb => cb.checked);
localStorage.setItem(STORAGE_KEY, JSON.stringify(checked));
}
checkboxes.forEach(cb => {
cb.addEventListener('change', () => {
cb.parentElement.classList.toggle('checked', cb.checked);
updateProgress();
});
});
document.getElementById('reset-btn').addEventListener('click', () => {
if (!confirm('Alle Häkchen wirklich zurücksetzen?')) return;
checkboxes.forEach(cb => {
cb.checked = false;
cb.parentElement.classList.remove('checked');
});
updateProgress();
});
// Theme-Toggle
const themeBtn = document.getElementById('theme-toggle');
function updateThemeIcon() {
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
themeBtn.textContent = cur === 'dark' ? '☀️' : '🌙';
themeBtn.title = cur === 'dark' ? 'Light Mode aktivieren' : 'Dark Mode aktivieren';
}
updateThemeIcon();
themeBtn.addEventListener('click', () => {
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
const next = cur === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('jga-theme', next);
updateThemeIcon();
});
updateProgress();
</script>
<script>
// 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>