Automatischer öffentlicher Start um 21 Uhr + Countdown auf der Zugangsseite

Nutzer-Wunsch 21.08.2026: "ich will dass du auch ein countdown zu der
seite hinzufügst wo wir den zugangscode eingeben müssen. den um 21h heute
abend geht die seite live. kannst du das sogar so anpassen dass es
automatisch läuft?"

server/gate.js:
- Neue Einstellung SITE_PUBLIC_LAUNCH_AT (ISO-Zeitstempel mit Zeitzone).
  Ab diesem Moment lässt gateMiddleware ausnahmslos jeden durch -- ganz
  ohne Neustart oder manuellen Eingriff, weil jede Anfrage die aktuelle
  Serverzeit live neu prüft. Die Freischaltung "passiert" also von selbst
  in der Sekunde, in der die Uhrzeit erreicht wird. Vorher bleiben die
  Zugangscodes unverändert nötig, damit das Team schon vorher rein kann.
  Fail-safe statt fail-open geprüft: ein kaputter/unparsbarer Zeitwert
  (z.B. Tippfehler in der .env) lässt die Schranke aktiv, statt die Seite
  versehentlich für alle zu öffnen.
- Neuer öffentlicher Endpunkt GET /gate-launch-info (immer erreichbar,
  auch ohne gültige Sitzung) liefert launchAt/isLive/serverTime für die
  Countdown-Anzeige im Frontend.

gate.html:
- Neue Countdown-Box zwischen Titel-Karte und den Zugangscode-Kacheln
  (bleibt unsichtbar, solange kein Starttermin konfiguriert ist). Rechnet
  auf der SERVERZEIT statt der eigenen Uhr (einmaliger Zeit-Abgleich beim
  Laden), damit eine falsch gehende Besucher-Uhr weder zu früh noch zu
  spät zählt. Bei Erreichen von Null folgt ein letzter Abgleich mit dem
  Server, bevor automatisch zur Zielseite weitergeleitet wird -- kein
  Klick, kein Neuladen nötig.
- Zugangscode-Kacheln (Dogi/VanVan/Diene) bleiben während des Countdowns
  unverändert nutzbar.

Getestet: 18 Middleware-Tests (inkl. Fail-safe bei kaputtem Zeitwert,
weiterhin funktionierender Zugangscode vor dem Start) + 10 Playwright-
Tests der Countdown-Oberfläche (Anzeige, Format, automatischer Sprung bei
Ablauf, sofortige Weiterleitung falls schon live, stiller Fallback bei
Netzwerkfehler). Zusätzlich alle 32 echten Seiten auf PC-Installierbarkeit
geprüft (Manifest, Icons, Service Worker, Install-Knopf, echter
Install-Klick-Ablauf simuliert) -- keine Probleme gefunden.

Cache-Busting-Version auf 20260821s erhöht.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-21 19:25:12 +02:00
co-authored by Claude Sonnet 5
parent 41faf22614
commit 57966e24d0
37 changed files with 408 additions and 243 deletions
+120 -2
View File
@@ -26,8 +26,8 @@
<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="DogFather" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821r" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821r" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821s" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821s" />
<style>
/* =====================================================================
Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern
@@ -246,6 +246,37 @@
.gate-cards { flex-direction: column; align-items: center; }
.gate-card { max-width: 300px; width: 100%; }
}
/* ---------- Countdown zum öffentlichen Start (21.08.2026) ----------
Nutzer-Wunsch: "ich will dass du auch ein countdown zu der seite
hinzufügst wo wir den zugangscode eingeben müssen. den um 21h heute
abend geht die seite live." Eigene, ruhige Optik statt einer reinen
Zahlenanzeige — passt zur restlichen Gold/Aurora-Bühne der Seite.
[hidden] per Default: erscheint erst, wenn server/gate.js per
/gate-launch-info wirklich einen Start-Zeitpunkt meldet (siehe Skript
unten) — ohne das bleibt die Seite unverändert wie vorher. */
.gate-countdown {
position: relative; z-index: 2; width: 100%; text-align: center;
margin-top: 1.3rem; padding: 1.1rem 1.4rem;
border-radius: 16px; border: 1.5px solid rgba(212,175,55,.35);
background: linear-gradient(180deg, rgba(20,23,29,.85), rgba(10,12,15,.9));
box-shadow: 0 20px 50px -25px rgba(0,0,0,.7);
}
.gate-countdown[hidden] { display: none; }
.gate-countdown-label {
margin: 0 0 .5rem; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase;
color: var(--text-muted);
}
.gate-countdown-zeit {
margin: 0; font-family: var(--font-head); font-weight: 800; letter-spacing: .04em;
font-size: clamp(1.7rem, 6vw, 2.4rem); font-variant-numeric: tabular-nums;
background: linear-gradient(135deg, #fff, #ffe9a8 55%, #d4af37);
-webkit-background-clip: text; background-clip: text; color: transparent;
animation: gate-countdown-puls 2.4s ease-in-out infinite;
}
@keyframes gate-countdown-puls { 0%,100% { opacity: .92; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .gate-countdown-zeit { animation: none; } }
.gate-countdown-hinweis { margin: .55rem 0 0; font-size: .82rem; color: var(--text-muted); }
</style>
</head>
<body>
@@ -269,6 +300,15 @@
</p>
</div>
<!-- Countdown zum öffentlichen Start (21.08.2026) — bleibt leer/unsichtbar,
solange server/gate.js keinen SITE_PUBLIC_LAUNCH_AT meldet (siehe
/gate-launch-info im Skript unten). -->
<div class="gate-countdown" id="gate-countdown" hidden>
<p class="gate-countdown-label">🎉 Startet automatisch in</p>
<p class="gate-countdown-zeit" id="gate-countdown-zeit">--:--:--</p>
<p class="gate-countdown-hinweis">Kein Neuladen nötig — die Seite öffnet sich von selbst.</p>
</div>
<!-- Nutzer-Wunsch 07.08.2026: statt einem gemeinsamen Eingabefeld für
Dogi/VanVan jetzt 2 getrennte Kacheln, gleiches Muster wie schon in
verwaltung.html — beide Formulare rufen weiterhin denselben
@@ -405,6 +445,84 @@
bindeGateKachel("gate-form-vanvan", "gate-code-vanvan", "gate-fehler-vanvan");
bindeGateKachel("gate-form-diene", "gate-code-diene", "gate-fehler-diene");
/* ---------- Countdown zum öffentlichen Start (21.08.2026) ----------
Nutzer-Wunsch: "um 21h heute abend geht die seite live. kannst du das
sogar so anpassen dass es automatisch läuft?" -- server/gate.js meldet
über /gate-launch-info den echten Start-Zeitpunkt (SITE_PUBLIC_LAUNCH_AT
in der .env) und lässt ab genau diesem Moment jeden ohne Code durch.
Diese Uhr hier ist rein die SICHTBARE Anzeige dafür + der automatische
Sprung zur Zielseite, sobald es soweit ist -- die eigentliche
Freischaltung passiert unabhängig davon serverseitig.
Läuft bewusst auf der SERVERZEIT, nicht der eigenen Uhr: viele Handys/
PCs gehen ein paar Minuten falsch, das würde sonst entweder zu früh
"00:00:00" zeigen (und dann an der noch verschlossenen Schranke
scheitern) oder unnötig lange nachzählen. offsetMs wird einmal beim
Laden bestimmt (Server-Zeit minus eigene Zeit) und auf die eigene,
lokal tickende Uhr addiert -- kein Dauer-Nachfragen nötig, nur beim
Erreichen von Null ein letzter Abgleich als Sicherheitsnetz. */
const countdownBox = document.getElementById("gate-countdown");
const countdownZeit = document.getElementById("gate-countdown-zeit");
function formatiereCountdown(ms) {
const gesamtSekunden = Math.max(0, Math.round(ms / 1000));
const std = Math.floor(gesamtSekunden / 3600);
const min = Math.floor((gesamtSekunden % 3600) / 60);
const sek = gesamtSekunden % 60;
const zweistellig = (n) => String(n).padStart(2, "0");
return `${zweistellig(std)}:${zweistellig(min)}:${zweistellig(sek)}`;
}
async function holeLaunchInfo() {
try {
const res = await fetch("/gate-launch-info", { cache: "no-store" });
const data = await res.json();
return data && data.ok ? data : null;
} catch {
return null;
}
}
if (countdownBox && countdownZeit) {
async function richteCountdownEin(info) {
const launchAtMs = new Date(info.launchAt).getTime();
const offsetMs = new Date(info.serverTime).getTime() - Date.now();
countdownBox.hidden = false;
const timerId = setInterval(async () => {
const serverJetzt = Date.now() + offsetMs;
const rest = launchAtMs - serverJetzt;
if (rest > 0) {
countdownZeit.textContent = formatiereCountdown(rest);
return;
}
countdownZeit.textContent = "00:00:00";
clearInterval(timerId);
// Letzter Abgleich mit dem Server statt blind der eigenen Uhr zu
// vertrauen -- erst wenn ER bestätigt "live", wird weitergeleitet.
// Sagt er "noch nicht" (z.B. Uhr wich ein paar Sekunden ab, oder
// der Zeitpunkt wurde inzwischen verschoben), richtet sich der
// Countdown mit den frischen Werten einfach neu ein, statt
// stumm bei "00:00:00" stehen zu bleiben.
const bestaetigung = await holeLaunchInfo();
if (bestaetigung && bestaetigung.isLive) location.href = next;
else if (bestaetigung && bestaetigung.launchAt) richteCountdownEin(bestaetigung);
}, 1000);
}
(async function starteCountdown() {
const info = await holeLaunchInfo();
if (!info || !info.launchAt) return; // kein Starttermin gesetzt -> Box bleibt versteckt
if (info.isLive) {
location.href = next; // schon live (z.B. Seite kurz nach dem Start neu geöffnet)
return;
}
richteCountdownEin(info);
})();
}
/* Service-Worker-Registrierung — auf allen anderen Seiten macht das main.js, diese Seite
ist aber bewusst eigenstaendig und laedt main.js nicht. Ohne registrierten Service
Worker bietet Chrome hier gar kein "App installieren" an (siehe Kommentar im Kopfbereich).