Nutzer-Wunsch 21.08.2026: erstes Fan-Treffen von Dogi & Tili (Team TiliDog) in Wuppertal ergänzt -- gleichzeitig das erste persönliche Aufeinandertreffen von DogFather und Tili. Als Meilenstein markiert und chronologisch korrekt zwischen "Juli 2026 — Start als Manager" und "27. Juli bis 2. August 2026 — Creator Cup" einsortiert. Cache-Busting-Version auf 20260821e erhöht. Co-Authored-By: Claude Sonnet 5 <[email protected]>
391 lines
22 KiB
HTML
391 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de" data-theme="dogfather">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Zugang — DOGFATHER UNIVERSE</title>
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
<link rel="icon" type="image/png" href="assets/img/favicon.png" />
|
|
<!-- BUGFIX (19.08.2026, Nutzer-Report vom Handy: "hab es installiert und klappt nicht"):
|
|
Diese Seite ist der ERSTE Bildschirm, den man ueberhaupt zu sehen bekommt (ohne gueltige
|
|
Sitzung leitet gate.js JEDEN Aufruf hierher um) -- also auch der Bildschirm, von dem aus
|
|
man die App installiert. Sie hatte aber weder Manifest-Verweis noch Service-Worker-
|
|
Registrierung, und beides verlangt Chrome, damit ueberhaupt eine echte, installierbare
|
|
App entsteht. Ergebnis: Wer vor dem ersten Login installiert hat, bekam bestenfalls eine
|
|
nackte Verknuepfung ohne Icon/Standalone-Modus. Exakt derselbe Fehler wie am 07.08.2026
|
|
in VanVans Shop (dort gefixt) -- hier war er nur nie aufgefallen, weil die Seite hinter
|
|
der Schranke (index.html) das alles laengst hatte. -->
|
|
<link rel="manifest" href="/manifest.json" />
|
|
<meta name="theme-color" content="#0b0d10" />
|
|
<!-- iOS ignoriert das Manifest fuer das Startbildschirm-Symbol komplett und nimmt NUR
|
|
apple-touch-icon. Fehlt es, zeigt iPhone/iPad einen unscharfen Screenshot der Seite
|
|
statt des Logos. Bewusst eine eigene Datei OHNE Transparenz: iOS ersetzt Alpha durch
|
|
Schwarz, und icon-512.png hat transparente Ecken (nachgemessen) -> haette schwarze
|
|
Ecken gegeben. -->
|
|
<link rel="apple-touch-icon" href="/assets/img/apple-touch-icon.png" />
|
|
<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=20260821e" />
|
|
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821e" />
|
|
<style>
|
|
/* =====================================================================
|
|
Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern
|
|
die volle Bühne für das Logo (Nutzer-Wunsch 03.08.2026: "übelst geil",
|
|
"überrasch mich", Logo verwenden). Reines CSS, keine zusätzlichen
|
|
Bild-Assets nötig — nur das vorhandene Logo + prozedural erzeugte
|
|
Sterne/Funken (siehe Skript unten, gleiche Machart wie der
|
|
Nebel-Effekt in main.js).
|
|
|
|
Lektion aus diesem Projekt beachtet: rotierende Ringe verzerren sich
|
|
auf nicht-runden Formen (siehe TikTok-Button-Lektion) — hier deshalb
|
|
bewusst KEIN rotierender Ring um das (unregelmäßig geformte) Logo,
|
|
stattdessen nur eine ruhig pulsierende Glut dahinter. overflow:hidden
|
|
am Body sorgt dafür, dass nichts über den Rand hinausblutet.
|
|
===================================================================== */
|
|
html, body { height: 100%; }
|
|
body {
|
|
/* align-items:center zuerst als Fallback fuer sehr alte Browser, die "safe" nicht
|
|
kennen (eine ungueltige Deklaration wird ignoriert, die vorige bleibt bestehen) --
|
|
align-items:safe center sorgt dafuer, dass zentrierter Inhalt beim Scrollen nie am
|
|
Anfang "abgeschnitten" wirkt. */
|
|
display: flex; align-items: center; align-items: safe center; justify-content: center;
|
|
min-height: 100vh; padding: 2rem 1.2rem; position: relative;
|
|
/* BUGFIX (07.08.2026, Nutzer-Report vom Handy: "Code eintippen geht, aber dann ist
|
|
der 'Weiter'-Knopf nirgendwo"): overflow:hidden auf dem BODY verhinderte jegliches
|
|
Scrollen -- sobald die virtuelle Tastatur einen grossen Teil des Bildschirms
|
|
einnimmt, ragte der Knopf unterhalb der sichtbaren Flaeche heraus und war komplett
|
|
unerreichbar, ohne jede Moeglichkeit hinzuscrollen (mit echter Tastatur-Simulation
|
|
nachgestellt und bestaetigt). overflow-y:auto erlaubt Scrollen NUR wenn wirklich
|
|
noetig -- im Normalfall ohne Tastatur aendert sich optisch nichts. overflow-x
|
|
bleibt hidden, das war der eigentliche urspruengliche Zweck (verhindert einen
|
|
ungewollten horizontalen Scrollbalken durch die Aurora-Nebel-Kreise, die
|
|
absichtlich ueber den Bildschirmrand hinausragen). */
|
|
overflow-x: hidden; overflow-y: auto; background: radial-gradient(ellipse at 50% 30%, #12161d 0%, #06070a 62%, #030304 100%);
|
|
}
|
|
|
|
/* ---------- Aurora-Nebel, langsam driftend (3 Farbtöne der Marke) ---------- */
|
|
.gate-aurora { position: fixed; inset: -10%; z-index: 0; pointer-events: none; filter: blur(2px); }
|
|
.gate-aurora span { position: absolute; border-radius: 50%; opacity: .5; filter: blur(70px); mix-blend-mode: screen; }
|
|
.gate-aurora .a1 { width: 55vw; height: 55vw; left: -10%; top: -8%; background: radial-gradient(circle, rgba(143,217,234,.55), transparent 70%); animation: gate-drift-1 26s ease-in-out infinite; }
|
|
.gate-aurora .a2 { width: 50vw; height: 50vw; right: -12%; top: 8%; background: radial-gradient(circle, rgba(139,92,246,.5), transparent 70%); animation: gate-drift-2 32s ease-in-out infinite; }
|
|
.gate-aurora .a3 { width: 46vw; height: 46vw; left: 20%; bottom: -18%; background: radial-gradient(circle, rgba(212,175,55,.4), transparent 70%); animation: gate-drift-3 38s ease-in-out infinite; }
|
|
@keyframes gate-drift-1 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(6vw,4vh) scale(1.12); } }
|
|
@keyframes gate-drift-2 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(-5vw,5vh) scale(1.08); } }
|
|
@keyframes gate-drift-3 { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(4vw,-4vh) scale(1.1); } }
|
|
@media (prefers-reduced-motion: reduce) { .gate-aurora span { animation: none; } }
|
|
|
|
/* ---------- Sternenstaub/Funken (per JS positioniert, siehe unten) ---------- */
|
|
.gate-funken { position: fixed; inset: 0; z-index: 1; pointer-events: none; }
|
|
.gate-funken span {
|
|
position: absolute; width: 3px; height: 3px; border-radius: 50%;
|
|
background: #ffe9a8; box-shadow: 0 0 6px 1px rgba(255,233,168,.9);
|
|
animation: gate-funke-twinkle 3.5s ease-in-out infinite;
|
|
}
|
|
@keyframes gate-funke-twinkle { 0%,100% { opacity: .15; transform: scale(.7); } 50% { opacity: 1; transform: scale(1.3); } }
|
|
@media (prefers-reduced-motion: reduce) { .gate-funken span { animation: none; opacity: .5; } }
|
|
|
|
/* ---------- Bühne: Logo + Karte übereinander ----------
|
|
Nutzer-Feedback 03.08.2026: das Logo sollte nicht wie ein Sticker in
|
|
einer weißen Kreisscheibe auf dem Hintergrund "kleben", sondern direkt
|
|
IM Hintergrund stecken. Grund für den weißen Kreis vorher: die
|
|
Original-PNG hatte selbst einen weißen Hintergrund eingebrannt (kein
|
|
Alphakanal). Freigestellt (Rand-Flood-Fill in Python/Pillow, nur die
|
|
mit dem Bildrand verbundene Fläche wurde transparent gemacht — innen-
|
|
liegende helle Stellen wie Augen-Glanzlicht blieben unangetastet) nach
|
|
logo-dogfather-transparent.png. Dadurch kein Kreis-Container/Ring mehr
|
|
nötig — nur noch eine weiche Glut dahinter, die durch die jetzt echten
|
|
Konturen des Hunde-Kopfs hindurch scheint. */
|
|
.gate-stage { position: relative; z-index: 2; display: flex; flex-direction: column; align-items: center; width: 100%; max-width: 440px; }
|
|
|
|
.gate-logo-wrap {
|
|
position: relative; width: 260px; margin-bottom: -34px; z-index: 3;
|
|
animation: gate-logo-float 6s ease-in-out infinite;
|
|
}
|
|
@keyframes gate-logo-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
|
|
@media (prefers-reduced-motion: reduce) { .gate-logo-wrap { animation: none; } }
|
|
|
|
/* Pulsierende Glut hinter der freigestellten Form (rund, aber sitzt HINTER
|
|
dem Logo statt es einzurahmen — scheint an den Konturen vorbei durch). */
|
|
.gate-logo-glow {
|
|
position: absolute; inset: -22%; border-radius: 50%; z-index: -1;
|
|
background: radial-gradient(circle, rgba(255,233,168,.65) 0%, rgba(143,217,234,.35) 40%, rgba(139,92,246,.2) 62%, transparent 76%);
|
|
filter: blur(26px); animation: gate-glow-puls 3.2s ease-in-out infinite;
|
|
}
|
|
@keyframes gate-glow-puls { 0%,100% { opacity: .6; transform: scale(.92); } 50% { opacity: 1; transform: scale(1.05); } }
|
|
|
|
.gate-logo-img {
|
|
position: relative; display: block; width: 100%; height: auto;
|
|
filter: drop-shadow(0 0 18px rgba(255,233,168,.5)) drop-shadow(0 22px 34px rgba(0,0,0,.65));
|
|
}
|
|
|
|
/* ---------- Die eigentliche Karte ---------- */
|
|
.gate-box {
|
|
position: relative; width: 100%; text-align: center;
|
|
padding: 4.2rem 2rem 2.4rem;
|
|
background: linear-gradient(180deg, rgba(20,23,29,.92), rgba(10,12,15,.96));
|
|
backdrop-filter: blur(14px);
|
|
border: 1.5px solid rgba(212,175,55,.4);
|
|
box-shadow: 0 0 0 1px rgba(212,175,55,.06) inset, 0 40px 80px -30px rgba(0,0,0,.75);
|
|
overflow: hidden;
|
|
}
|
|
.gate-box::before {
|
|
content: ""; position: absolute; top: 0; left: 8%; right: 8%; height: 1px;
|
|
background: linear-gradient(90deg, transparent, rgba(255,214,102,.95), transparent);
|
|
animation: gate-goldglanz 4.5s ease-in-out infinite;
|
|
}
|
|
@keyframes gate-goldglanz { 0%,100% { opacity: .3; } 50% { opacity: 1; } }
|
|
|
|
.gate-box .eyebrow { color: #ffe9a8; border-color: rgba(255,214,102,.5); }
|
|
.gate-box h1 {
|
|
font-size: clamp(1.6rem, 4vw, 2.1rem); margin: .5rem 0 .7rem; letter-spacing: -.01em;
|
|
background: linear-gradient(135deg, #fff, #ffe9a8 55%, #d4af37);
|
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
}
|
|
.gate-box p { font-size: 1rem; }
|
|
.gate-box input {
|
|
width: 100%; box-sizing: border-box; padding: 1rem 1.1rem; border-radius: 12px; text-align: center;
|
|
letter-spacing: .1em; font-size: 1.1rem; font-weight: 600; margin: 1.4rem 0 1rem;
|
|
background: rgba(255,255,255,.04); border: 1.5px solid var(--border); color: var(--text);
|
|
transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
|
|
}
|
|
.gate-box input::placeholder { letter-spacing: normal; font-weight: 400; color: rgba(154,164,178,.55); }
|
|
.gate-box input:focus {
|
|
outline: none; border-color: rgba(212,175,55,.7); background: rgba(255,255,255,.06);
|
|
box-shadow: 0 0 0 4px rgba(212,175,55,.14);
|
|
}
|
|
/* "Weiter"-Knopf: der schon fertig gebaute, aber bislang ungenutzte
|
|
.btn-holo-Stil (main.css) — Trading-Card-Holo-Folie in genau den drei
|
|
Farben, die auch schon den Hintergrund dieser Seite tragen (Babyblau/
|
|
Lila/Gold, siehe .gate-aurora), daher hier bewusst statt des
|
|
generischen .btn-primary. Volle Breite + zentrierter Text sind eine
|
|
seiteneigene Ergänzung (der Stil selbst ist an anderer Stelle als
|
|
Inline-Button mit automatischer Breite gedacht). */
|
|
.gate-box .btn-holo { width: 100%; justify-content: center; margin-top: .2rem; }
|
|
.gate-fehler { color: #ff9a9a; font-size: .9rem; margin-top: .9rem; display: none; }
|
|
.gate-fehler.zeige { display: block; }
|
|
|
|
/* ---------- Zwei getrennte Zugangskacheln (Dogi/VanVan) statt einer
|
|
gemeinsamen Box — Nutzer-Wunsch 07.08.2026 ("Eingang von VanVan wie bei
|
|
ihrer normalen Seite mit den 2 Kacheln"). Gleiches Muster wie schon in
|
|
verwaltung.html (.vw-gate-card) etabliert, hier nur farblich an die
|
|
bereits vorhandene Gold/Babyblau-Optik dieser Seite angepasst. Nur die
|
|
Kacheln sind neu — Hintergrund/Logo/Bühne bleiben unverändert. ---------- */
|
|
.gate-cards {
|
|
position: relative; z-index: 2; display: flex; flex-wrap: wrap; gap: 1.1rem;
|
|
justify-content: center; width: 100%; margin-top: 1.3rem;
|
|
}
|
|
.gate-card {
|
|
position: relative; isolation: isolate; overflow: hidden;
|
|
flex: 1 1 220px; max-width: 280px; text-align: center;
|
|
padding: 1.6rem 1.3rem 1.4rem; border-radius: 18px; border: 1.5px solid transparent;
|
|
background:
|
|
linear-gradient(180deg, rgba(20,23,29,.92), rgba(10,12,15,.96)) padding-box,
|
|
linear-gradient(110deg, #8fd9ea, #38bdf8, #d4af37, #8fd9ea) border-box;
|
|
background-size: 100% 100%, 320% 100%;
|
|
box-shadow: 0 30px 70px -25px rgba(0,0,0,.75), 0 0 50px -20px rgba(143,217,234,.3);
|
|
}
|
|
/* VanVans Kachel: eigene, femininere Farbwelt (Rosé/Lavendel/Gold statt
|
|
Babyblau) — gleiches Prinzip wie in verwaltung.html, keine bloße
|
|
Umfärbung von Dogis Kachel. */
|
|
.gate-card--vanvan {
|
|
background:
|
|
linear-gradient(180deg, rgba(24,15,20,.92), rgba(15,9,13,.96)) padding-box,
|
|
linear-gradient(110deg, #ff8fc6, #c084fc, #d4af37, #ff8fc6) border-box;
|
|
box-shadow: 0 30px 70px -25px rgba(0,0,0,.75), 0 0 50px -20px rgba(255,143,198,.35);
|
|
}
|
|
.gate-card-icon {
|
|
display: block; width: 60px; height: 60px; margin: 0 auto .6rem; border-radius: 50%;
|
|
background-size: cover; background-position: center;
|
|
border: 2px solid rgba(143,217,234,.6);
|
|
box-shadow: 0 0 18px rgba(143,217,234,.4), inset 0 0 10px rgba(255,255,255,.08);
|
|
}
|
|
.gate-card-icon--dogi { background-image: url('assets/img/vw-gate-dogi.jpg'); }
|
|
.gate-card-icon--vanvan { background-image: url('assets/img/vw-gate-vanvan.jpg'); }
|
|
.gate-card--vanvan .gate-card-icon { border-color: rgba(255,143,198,.65); }
|
|
.gate-card-name { font-size: 1.05rem; font-weight: 800; margin: 0 0 .8rem; color: var(--text); }
|
|
.gate-card--vanvan .gate-card-name { color: #ffd1e8; }
|
|
.gate-card input {
|
|
width: 100%; box-sizing: border-box; padding: .8rem .9rem; border-radius: 12px; text-align: center;
|
|
letter-spacing: .1em; font-size: 1rem; font-weight: 600; margin-bottom: .9rem;
|
|
background: rgba(255,255,255,.04); border: 1.5px solid var(--border); color: var(--text);
|
|
transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
|
|
}
|
|
.gate-card input::placeholder { letter-spacing: normal; font-weight: 400; color: rgba(154,164,178,.55); }
|
|
.gate-card input:focus {
|
|
outline: none; border-color: rgba(212,175,55,.7); background: rgba(255,255,255,.06);
|
|
box-shadow: 0 0 0 4px rgba(212,175,55,.14);
|
|
}
|
|
.gate-card--vanvan input:focus { border-color: rgba(255,143,198,.7); box-shadow: 0 0 0 4px rgba(255,143,198,.16); }
|
|
.gate-card .btn-holo.gate-card-btn { width: 100%; justify-content: center; padding: .65rem 1rem; font-size: .88rem; gap: .3rem; }
|
|
.gate-card-btn--vanvan {
|
|
background: linear-gradient(120deg, #ff8fc6 0%, #c084fc 30%, #ffd76a 55%, #ff8fc6 80%, #c084fc 100%);
|
|
background-size: 340% 340%;
|
|
}
|
|
.gate-card .gate-fehler { margin-top: .7rem; }
|
|
@media (max-width: 480px) {
|
|
.gate-cards { flex-direction: column; align-items: center; }
|
|
.gate-card { max-width: 300px; width: 100%; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="gate-aurora" aria-hidden="true"><span class="a1"></span><span class="a2"></span><span class="a3"></span></div>
|
|
<div class="gate-funken" id="gate-funken" aria-hidden="true"></div>
|
|
|
|
<div class="gate-stage">
|
|
<div class="gate-logo-wrap" aria-hidden="true">
|
|
<div class="gate-logo-glow"></div>
|
|
<img class="gate-logo-img" src="assets/img/logo-dogfather-transparent.png" alt="" width="472" height="476" />
|
|
</div>
|
|
|
|
<div class="card gate-box">
|
|
<!-- Nutzer-Wunsch 04.08.2026: Besucher ohne Zugangscode (z.B. über einen
|
|
gefundenen/geteilten Link) sollen HIER nicht mehr "Noch nicht
|
|
öffentlich / im Aufbau" lesen — stattdessen ein normal wirkender
|
|
Link auf die eigene Domain (die spätere, öffentliche Hauptseite). -->
|
|
<h1>DOGFATHER UNIVERSE</h1>
|
|
<p class="small" style="opacity:.85;">
|
|
<a href="https://dogfather-universe.com/" style="color:#8fd9ea; font-weight:700; text-decoration: underline; text-underline-offset: .2em;">dogfather-universe.com</a>
|
|
</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
|
|
/gate-auth-Endpunkt auf, der Zugangscode selbst entscheidet
|
|
serverseitig, wer sich anmeldet (siehe server/gate.js). -->
|
|
<div class="gate-cards">
|
|
<form id="gate-form-dogi" class="gate-card gate-card--dogi" novalidate>
|
|
<span class="gate-card-icon gate-card-icon--dogi" aria-hidden="true"></span>
|
|
<h2 class="gate-card-name">Dogi</h2>
|
|
<input type="text" id="gate-code-dogi" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" autofocus />
|
|
<button type="submit" class="btn-holo gate-card-btn">
|
|
<span class="btn-holo-text">💙 Öffnen</span>
|
|
</button>
|
|
<p class="gate-fehler" id="gate-fehler-dogi"></p>
|
|
</form>
|
|
|
|
<form id="gate-form-vanvan" class="gate-card gate-card--vanvan" novalidate>
|
|
<span class="gate-card-icon gate-card-icon--vanvan" aria-hidden="true"></span>
|
|
<h2 class="gate-card-name">VanVan</h2>
|
|
<input type="text" id="gate-code-vanvan" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" />
|
|
<button type="submit" class="btn-holo gate-card-btn gate-card-btn--vanvan">
|
|
<span class="btn-holo-text">💗 Öffnen</span>
|
|
</button>
|
|
<p class="gate-fehler" id="gate-fehler-vanvan"></p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
// Sternenstaub — gleiche Machart wie der Nebel-Effekt in main.js
|
|
// (renderFog): zufällig positionierte, unterschiedlich große/verzögerte
|
|
// Punkte statt einer festen Deko.
|
|
const feld = document.getElementById("gate-funken");
|
|
let html = "";
|
|
for (let i = 0; i < 44; i++) {
|
|
const size = 2 + Math.round(Math.random() * 3);
|
|
const top = Math.round(Math.random() * 100);
|
|
const left = Math.round(Math.random() * 100);
|
|
const delay = (Math.random() * 3.5).toFixed(1);
|
|
const dauer = (2.5 + Math.random() * 2.5).toFixed(1);
|
|
html += `<span style="width:${size}px;height:${size}px;top:${top}%;left:${left}%;animation-delay:${delay}s;animation-duration:${dauer}s;"></span>`;
|
|
}
|
|
feld.innerHTML = html;
|
|
|
|
const params = new URLSearchParams(location.search);
|
|
const next = params.get("next") || "/index.html";
|
|
|
|
// Beide Kacheln (Dogi/VanVan) nutzen dieselbe Logik und denselben
|
|
// /gate-auth-Endpunkt — nur das jeweils eigene Formular/Eingabefeld/
|
|
// Fehlerfeld unterscheidet sich.
|
|
/* BUGFIX (19.08.2026, Nutzer-Report vom Handy): Der Knopf gab beim Antippen KEINERLEI
|
|
Rueckmeldung und die Anfrage hatte kein Zeitlimit. Bleibt im Mobilfunknetz eine Antwort
|
|
aus, haengt fetch() unbegrenzt -- fuer die Person sieht es exakt so aus, als sei der Tap
|
|
gar nicht angekommen ("er nimmt meinen Code nicht an"), obwohl in Wahrheit nur die
|
|
Antwort fehlt. Zusaetzlich liess sich mehrfach hintereinander absenden, was bei der
|
|
5-Versuche-Sperre in server/gate.js besonders unguenstig ist: Doppel-Taps zaehlen als
|
|
mehrere Fehlversuche und koennen die eigene IP fuer 15 Minuten aussperren.
|
|
Derselbe Fix wie am 06.08.2026 in VanVans gate.html/verwaltung-code.html. */
|
|
const ZEITLIMIT_MS = 10000;
|
|
|
|
function bindeGateKachel(formId, inputId, fehlerId) {
|
|
const form = document.getElementById(formId);
|
|
const fehlerEl = document.getElementById(fehlerId);
|
|
const btn = form.querySelector("button[type=submit]");
|
|
const btnText = btn ? btn.querySelector(".btn-holo-text") : null;
|
|
const originalText = btnText ? btnText.textContent : "";
|
|
let laeuft = false;
|
|
|
|
function sperren(an) {
|
|
laeuft = an;
|
|
if (btn) btn.disabled = an;
|
|
if (btnText) btnText.textContent = an ? "Wird geprüft…" : originalText;
|
|
form.setAttribute("aria-busy", an ? "true" : "false");
|
|
}
|
|
|
|
form.addEventListener("submit", async function (e) {
|
|
e.preventDefault();
|
|
if (laeuft) return; // Doppel-Tap ignorieren
|
|
fehlerEl.classList.remove("zeige");
|
|
const code = document.getElementById(inputId).value.trim();
|
|
if (!code) {
|
|
fehlerEl.textContent = "Bitte zuerst den Zugangscode eingeben.";
|
|
fehlerEl.classList.add("zeige");
|
|
return;
|
|
}
|
|
|
|
sperren(true);
|
|
/* AbortController statt endlosem Warten. AbortSignal.timeout() waere kuerzer, kennen
|
|
aeltere Handy-Browser aber nicht -- deshalb bewusst der breiter unterstuetzte Weg. */
|
|
const abbruch = new AbortController();
|
|
const timer = setTimeout(() => abbruch.abort(), ZEITLIMIT_MS);
|
|
try {
|
|
const res = await fetch("/gate-auth", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ code, next }),
|
|
signal: abbruch.signal,
|
|
});
|
|
const data = await res.json().catch(() => null);
|
|
if (!res.ok || !data || !data.ok) {
|
|
fehlerEl.textContent = (data && data.error) || "Falscher Zugangscode.";
|
|
fehlerEl.classList.add("zeige");
|
|
sperren(false);
|
|
return;
|
|
}
|
|
/* Erfolg: Knopf bewusst gesperrt lassen — die Seite wird gleich gewechselt, ein
|
|
erneuter Tap in der Zwischenzeit waere nur verwirrend. */
|
|
if (btnText) btnText.textContent = "Willkommen 💙";
|
|
location.href = data.next || "/index.html";
|
|
} catch (err) {
|
|
fehlerEl.textContent =
|
|
err && err.name === "AbortError"
|
|
? "Zeitüberschreitung — Verbindung zu langsam. Bitte nochmal versuchen."
|
|
: "Verbindung fehlgeschlagen. Bitte nochmal versuchen.";
|
|
fehlerEl.classList.add("zeige");
|
|
sperren(false);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
});
|
|
}
|
|
bindeGateKachel("gate-form-dogi", "gate-code-dogi", "gate-fehler-dogi");
|
|
bindeGateKachel("gate-form-vanvan", "gate-code-vanvan", "gate-fehler-vanvan");
|
|
|
|
/* 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).
|
|
Bewusst identisch defensiv wie in main.js: nur ueber http(s), Fehler still schlucken. */
|
|
if ("serviceWorker" in navigator && /^https?:$/.test(location.protocol)) {
|
|
window.addEventListener("load", () => {
|
|
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|