Filipe, 30.09.2026: „wenn wir bei highlights videos rein setzen will
ich nicht mehr dass wir sie freigeben muessen, sobald die reingesetzt
wurden sollen die sofort zu sehen sein."
=====================================================================
WAS ICH NICHT GETAN HABE, UND WARUM NICHT
=====================================================================
Die naheliegende Loesung waere gewesen, `highlight` aus
`TREFF_FREIGABE_BRETTER` zu streichen. Das waere falsch: Auf diesem
Brett laedt auch die COMMUNITY hoch -- Clips, Bilder, Fanart. Im
Quelltext steht woertlich daneben:
„Zwei Schloesser, weil hier fremde Inhalte hochgeladen werden --
Urheberrecht und Anstand sind nichts, was man nachtraeglich
klaert."
Filipe meint nicht das. Er meint: „wenn WIR videos rein setzen".
=====================================================================
DIE UNTERSCHEIDUNG STAND SCHON IM HAUS -- nur nicht im Code
=====================================================================
Derselbe Quelltext sagt ueber die zwei Freigabe-Bretter
Verschiedenes:
ansteht -> der SCHALTER „Im Treff zeigen". Das Team entscheidet
JE TERMIN, ob die Community ihn sieht.
highlight -> der Urheberrechts- und Anstandsfilter.
Ein Filter fragt „hat das jemand angesehen?". Wenn der, der ihn
bedienen darf, den Eintrag SELBST anlegt, ist die Antwort ja. Genau
diese Begruendung steht seit dem Uebernehmen aus dem Katalog im
Haus: „Eine zweite daneben waere keine Sicherheit, sondern ein
Klick."
Ein Schalter dagegen ist eine Entscheidung je Fall. Termine bleiben
deshalb unberuehrt -- sonst stuende jeder interne Termin sofort im
Treff, und danach hat niemand gefragt.
Neu: `FREIGABE_IST_FILTER` und `sofortFreigeben()` in
workspace-treff.js. DIE BEDINGUNG FRAGT DIE ROLLE, NICHT DEN WEG --
waere der Weg gefragt, waere aus dem Filter ein Loch geworden, sobald
jemand einen zweiten Weg baut. Ein Community-Mitglied ab „Stamm"
darf weiterhin einstellen; sein Eintrag wartet auf das Team.
Gerufen an ZWEI Stellen: beim Videoweg und beim Anlegen von Hand.
Beide hatten es bisher nicht.
=====================================================================
DIE VORHANDENE FREIGABEPRUEFUNG BEWEIST DAS NICHT
=====================================================================
Sie blieb nach der Aenderung gruen -- und das zu Recht: Sie legt ihre
Eintraege unmittelbar in der Datenbank an und prueft damit den
Mechanismus, nicht den Weg. Haette ich mich darauf verlassen, waere
eine Aenderung ausgeliefert worden, fuer die keine Zeile spricht.
pruef-treff (+5): DogFather legt ueber den echten Weg an -> die
Community sieht es SOFORT. Ein TERMIN bleibt verborgen. Die Regel
gibt fuer eine Rolle von aussen NICHT frei und fuer Termine
ueberhaupt nicht.
pruef-video (+3): Der Videoweg hat seinen eigenen Aufruf -- genau
dort wird einer vergessen. Geprueft wird die Freigabezeile selbst,
samt Gegenprobe „freigegeben ist nur, was auch angelegt wurde".
Meinen Abschnitt hatte ich erst HINTER das Abschalten des
nachgebauten TikTok-Dienstes gehaengt -- der Kopf der Datei warnt
woertlich davor („das Abschalten steht ganz hinten"). Gelesen habe
ich ihn, als es rot wurde.
UND `pruef-struktur` HAT MEINE EIGENE NEUE ZEILE GEFANGEN: Sie bildete
das Datum aus UTC statt Ortszeit -- zwischen Mitternacht und zwei Uhr
waere es der falsche Tag gewesen. Behoben mit `tagLokal()`, Minuten
nach dem Schreiben.
Gemessen: pruef-treff 85/0 (war 80), pruef-video 74/0 (war 71),
pruef-highlights 31/0, pruef-bereiche-lesend 37/0,
pruef-treff-werkzeuge 73/0, pruef-alle-sehen-es 43/0,
pruef-community-sicht 10/0, pruef-wege-nach-draussen 67/0,
pruef-struktur 44/0.
Co-Authored-By: Claude Opus 5 <[email protected]>
561 lines
31 KiB
HTML
561 lines
31 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/app-symbole/universe-32.png?v=202609301254" />
|
|
<!-- 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="#065f76" />
|
|
<!-- 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/app-symbole/universe-180.png?v=202609301254" />
|
|
<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=202609301254" />
|
|
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=202609301254" />
|
|
<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?v=202609301254'); }
|
|
.gate-card-icon--vanvan { background-image: url('assets/img/vw-gate-vanvan.jpg?v=202609301254'); }
|
|
.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%;
|
|
}
|
|
/* Dienes Kachel (21.08.2026, Nutzer-Wunsch: "mach eine 3 Zugangskiste ...
|
|
ich will nämlich dass Diene schon jetzt auch die Seite sieht, damit wir
|
|
mit mehreren Augen die letzten kleinen Details verbessern"). Wieder eine
|
|
EIGENE Farbwelt (Smaragd/Mint/Gold) statt einer Umfärbung der beiden
|
|
anderen -- so ist auf einen Blick klar, welche Kachel wem gehört. */
|
|
.gate-card--diene {
|
|
background:
|
|
linear-gradient(180deg, rgba(13,24,20,.92), rgba(8,15,13,.96)) padding-box,
|
|
linear-gradient(110deg, #6ee7b7, #34d399, #d4af37, #6ee7b7) border-box;
|
|
box-shadow: 0 30px 70px -25px rgba(0,0,0,.75), 0 0 50px -20px rgba(52,211,153,.35);
|
|
}
|
|
.gate-card-icon--diene { background-image: url('assets/img/avatar-diene.jpg?v=202609301254'); }
|
|
.gate-card--diene .gate-card-icon { border-color: rgba(110,231,183,.65); }
|
|
.gate-card--diene .gate-card-name { color: #c7f5e3; }
|
|
.gate-card--diene input:focus { border-color: rgba(52,211,153,.7); box-shadow: 0 0 0 4px rgba(52,211,153,.16); }
|
|
.gate-card-btn--diene {
|
|
background: linear-gradient(120deg, #6ee7b7 0%, #34d399 30%, #ffd76a 55%, #6ee7b7 80%, #34d399 100%);
|
|
background-size: 340% 340%;
|
|
}
|
|
.gate-card .gate-fehler { margin-top: .7rem; }
|
|
@media (max-width: 480px) {
|
|
.gate-cards { flex-direction: column; align-items: center; }
|
|
/* Bugfix 21.08.2026 (Nutzer-Report per Bild: "sieht man die buttons
|
|
nicht ganz wenn man auf dem handy schaut"). Ursache: .gate-card trägt
|
|
"flex: 1 1 220px" für die Desktop-Reihe (220px = gewünschte BREITE).
|
|
Sobald .gate-cards hier auf flex-direction:column umschaltet, gilt
|
|
diese Zahl aber für die HÖHE, nicht mehr die Breite -- die Karte
|
|
wurde dadurch zwangsweise auf genau 220px Höhe gequetscht. Normal
|
|
verhindert das ein unsichtbares Mindestmaß (min-height:auto), aber
|
|
.gate-card hat "overflow:hidden" gesetzt, was dieses Mindestmaß laut
|
|
Spezifikation auf 0 zurücksetzt -- der "Öffnen"-Knopf (der bei
|
|
normaler, am Inhalt orientierter Höhe locker hineingepasst hätte)
|
|
wurde dadurch unten abgeschnitten. flex:none macht die Karte wieder
|
|
so hoch wie ihr Inhalt braucht, width:100% (samt max-width darüber)
|
|
bestimmt weiterhin allein die Breite. */
|
|
.gate-card { max-width: 300px; width: 100%; flex: none; }
|
|
}
|
|
|
|
/* ---------- 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>
|
|
<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?v=202609301254" 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>
|
|
|
|
<!-- 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
|
|
/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>
|
|
<!-- autofocus hier bewusst ENTFERNT (21.08.2026, Rückmeldung von Diene:
|
|
wenn ich bei mir Zugangscode eingebe, springt er danach zu dir hoch,
|
|
mein Name ist dann nicht mehr sichtbar). Auf dem Handy tippt man
|
|
unten in die eigene Kachel, während die Seite noch lädt — sobald sie
|
|
fertig ist, zieht autofocus den Cursor zurück in DIESES Feld ganz
|
|
oben, das Handy scrollt mit und die eigene Kachel ist weg. Bei drei
|
|
gleichberechtigten Kacheln ist ein vorausgewähltes Feld ohnehin
|
|
falsch: für zwei von drei Personen öffnet sich die Tastatur an der
|
|
falschen Stelle. -->
|
|
<input type="text" id="gate-code-dogi" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" />
|
|
<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>
|
|
|
|
<form id="gate-form-diene" class="gate-card gate-card--diene" novalidate>
|
|
<span class="gate-card-icon gate-card-icon--diene" aria-hidden="true"></span>
|
|
<h2 class="gate-card-name">Diene</h2>
|
|
<input type="text" id="gate-code-diene" placeholder="Zugangscode" autocomplete="off" autocapitalize="off" spellcheck="false" />
|
|
<button type="submit" class="btn-holo gate-card-btn gate-card-btn--diene">
|
|
<span class="btn-holo-text">💚 Öffnen</span>
|
|
</button>
|
|
<p class="gate-fehler" id="gate-fehler-diene"></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");
|
|
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).
|
|
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>
|