Registrierung: E-Mail-Bestätigung per Einmalcode vor dem Zugangscode

Nutzer-Wunsch 21.08.2026: "bei der ersten registrierung sollen die eine
email bekommen mit einem einmaligen code damit wir auch wissen dass
email stimmt und danach wenn sie den code eingegeben haben sollen die
erst ihren zugangscode auswählen/eintippen können."

Damit wird eine echte Lücke geschlossen: registerSupporter() hat bisher
`email_verified = 1` gesetzt, OHNE dass irgendetwas geprüft wurde -- man
konnte sich mit einer fremden oder erfundenen Adresse registrieren und
kam sofort rein.

Neuer Ablauf in drei Schritten:
1. Name/TikTok/E-Mail -> Konto wird als UNBESTÄTIGT angelegt
   (email_verified = 0, noch kein Zugangscode). Es kommt bewusst KEIN
   Session-Token zurück -- eingeloggt ist man hier noch nicht.
2. Einmalcode aus der E-Mail eingeben -> verify-email bestätigt und
   loggt ein. Die Willkommens-Mail wandert hierher, sie ging vorher an
   eine noch ungeprüfte Adresse.
3. Erst jetzt den eigenen Zugangscode festlegen.

Serverseitig abgesichert: setSupporterAccessCode() lehnt ab, solange die
E-Mail nicht bestätigt ist -- der Schritt ist damit nicht nur im
Formular versteckt, sondern auch per Direktaufruf nicht überspringbar.

Wiederverwendet wird die bereits vorhandene Mechanik (issueAuthCode/
verifyAuthCode mit Zweck "verify_email", sendVerifyEmailCode, die Panels
#panel-code und #panel-neuer-code) -- der "Code vergessen?"-Weg nutzt
dieselbe Code-Eingabe und bleibt unverändert; eine neue Variable
codeZweck unterscheidet, welcher Endpunkt aufgerufen wird.

Mit Playwright end-to-end geprüft (14 Tests): Reihenfolge der Aufrufe,
kein Token vor der Bestätigung, falscher Code kommt nicht weiter,
Zugangscode-Feld erst im letzten Schritt, "Code vergessen?" unberührt.

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

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-21 17:48:08 +02:00
co-authored by Claude Opus 5
parent 5d0ea11570
commit 5a5912636d
37 changed files with 331 additions and 286 deletions
+6 -6
View File
@@ -18,10 +18,10 @@
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<meta name="robots" content="noindex" />
<title>Seite nicht gefunden — DOGFATHER UNIVERSE</title>
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body>
<div id="site-header"></div>
@@ -41,7 +41,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-404.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-404.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+34 -35
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Unterstütze Dogfather dauerhaft mit der DogiCrew 🩵 und sammle exklusive Treueprämien — die Teddy-Kollektion entsteht in Kooperation mit VanVan." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* =====================================================================
Preis-/Anmelde-Kiste — komplett neu gestaltet (Nutzer-Feedback
@@ -429,25 +429,15 @@
<label for="reg-email" data-i18n="ab_feld_email">E-Mail-Adresse</label>
<input type="email" id="reg-email" maxlength="120" />
</div>
<!-- Eigener, dauerhafter Zugangscode (21.08.2026, Nutzer-Wunsch:
"die leute sollen nach der registrierung einen festen code
erstellen können für sich selbst und fertig, mit dem sie rein
kommen"). type="password" — der Code soll beim Tippen nicht
für Umstehende lesbar sein. -->
<!-- Beschriftung geschärft 21.08.2026 (Nutzer-Rückmeldung: "auf
dieser seite brauch man doch keine zugangsdaten, da kann man
sich ja noch nicht einloggen"). Genau dieser Eindruck entstand,
weil "Dein Zugangscode" wie die Abfrage eines BESTEHENDEN Codes
klang. Jetzt steht als Aufforderung im Text, dass man ihn sich
hier gerade ausdenkt — plus ein Platzhalter im Feld selbst, der
das nochmal zeigt, falls jemand nur auf die Felder schaut. -->
<div class="abo-field">
<label for="reg-code" data-i18n="ab_feld_code_neu">Denk dir jetzt deinen Zugangscode aus (mindestens 6 Zeichen)</label>
<input type="password" id="reg-code" maxlength="200" autocomplete="new-password"
placeholder="Frei wählbar — z. B. DogiCrew2026"
data-i18n-attr="placeholder:ab_feld_code_platzhalter" />
<p class="small muted" style="margin:.45rem 0 0;" data-i18n="ab_code_hinweis_neu">Mit diesem Code loggst du dich künftig ein — merk ihn dir gut.</p>
</div>
<!-- Das Zugangscode-Feld stand bis 21.08.2026 hier, direkt neben den
Stammdaten. Es ist bewusst in einen eigenen, SPÄTEREN Schritt
gewandert (Nutzer-Wunsch: "bei der ersten registrierung sollen
die eine email bekommen mit einem einmaligen code damit wir auch
wissen dass email stimmt und danach wenn sie den code eingegeben
haben sollen die erst ihren zugangscode auswählen/eintippen
können"). Ablauf jetzt: hier Stammdaten -> Einmalcode per Mail
(#panel-code) -> Zugangscode festlegen (#panel-neuer-code). -->
<p class="small muted" style="margin:-.2rem 0 .2rem;" data-i18n="ab_reg_hinweis_bestaetigung">Wir schicken dir gleich einen 6-stelligen Code per E-Mail. Danach legst du deinen eigenen Zugangscode fest.</p>
<!-- Nutzer-Wunsch 21.08.2026: "die leute sollen sich auch schon
registrieren können, aber der abo soll noch zu sein, da soll
dann coming soon stehen." Registrierung/Login sind ab jetzt
@@ -613,9 +603,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-abonnieren.js?v=20260821n"></script>
<script src="assets/js/supporter.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-abonnieren.js?v=20260821o"></script>
<script src="assets/js/supporter.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
(function () {
const S = window.DogiSupporter;
@@ -661,6 +651,10 @@
// Eingabe des Einmalcodes (der frühere codePurpose-Schalter entfiel mit
// der Umstellung 21.08.2026 — es gibt nur noch diesen einen Code-Weg).
let codeEmail = "";
// Merkt sich, WARUM gerade ein Einmalcode eingegeben wird (21.08.2026):
// "register" = Erstbestätigung der E-Mail, "login" = "Code vergessen?".
// Steuert in btn-code-verify, welcher Endpunkt aufgerufen wird.
let codeZweck = "login";
async function pruefeStatus() {
if (!S.getToken()) {
@@ -832,15 +826,15 @@
const displayName = document.getElementById("reg-name").value.trim();
const tiktokUsername = document.getElementById("reg-tiktok").value.trim();
const email = document.getElementById("reg-email").value.trim();
const accessCode = document.getElementById("reg-code").value;
if (!displayName || !email) return fehlerZeigen(el, "Bitte Name und E-Mail-Adresse angeben.");
if (!accessCode) return fehlerZeigen(el, "Bitte leg einen Zugangscode fest.");
try {
// Seit 21.08.2026 liefert /supporter/register direkt ein Token zurück —
// kein Bestätigungscode per E-Mail mehr dazwischen, man ist sofort drin.
const data = await S.apiFetch("/supporter/register", { displayName, tiktokUsername, email, accessCode });
S.setToken(data.token);
await pruefeStatus();
// Schritt 1 von 3 (21.08.2026): /supporter/register legt nur das noch
// unbestätigte Konto an und verschickt einen Einmalcode -- es kommt
// bewusst KEIN Token zurück, eingeloggt ist man erst nach Schritt 2.
await S.apiFetch("/supporter/register", { displayName, tiktokUsername, email });
codeEmail = email;
codeZweck = "register";
zeige("panel-code");
} catch (err) {
fehlerZeigen(el, err.message);
}
@@ -871,6 +865,7 @@
try {
await S.apiFetch("/supporter/login-request", { email });
codeEmail = email;
codeZweck = "login";
zeige("panel-code");
} catch (err) {
fehlerZeigen(el, err.message);
@@ -883,9 +878,13 @@
const code = document.getElementById("code-input").value.trim();
if (!code) return fehlerZeigen(el, "Bitte Code eingeben.");
try {
const data = await S.apiFetch("/supporter/verify-login", { email: codeEmail, code });
// Dieselbe Code-Eingabe bedient zwei Wege (21.08.2026):
// "register" -> Erstbestätigung der E-Mail bei der Registrierung
// "login" -> "Code vergessen?"-Weg für bestehende Konten
// Beide enden gleich: eingeloggt, danach Zugangscode festlegen.
const endpunkt = codeZweck === "register" ? "/supporter/verify-email" : "/supporter/verify-login";
const data = await S.apiFetch(endpunkt, { email: codeEmail, code });
S.setToken(data.token);
// Eingeloggt — jetzt fehlt nur noch der neue dauerhafte Zugangscode.
zeige("panel-neuer-code");
} catch (err) {
fehlerZeigen(el, err.message);
+5 -5
View File
@@ -20,10 +20,10 @@
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<meta name="robots" content="noindex" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body>
<div id="site-header"></div>
@@ -134,6 +134,6 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Detailansicht einer Aktion oder eines Projekts von Team Dogi." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body>
<div id="site-header"></div>
@@ -51,9 +51,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-aktionen.js?v=20260821n"></script>
<script src="assets/js/i18n-aktion-detail.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-aktionen.js?v=20260821o"></script>
<script src="assets/js/i18n-aktion-detail.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const params = new URLSearchParams(location.search);
+10
View File
@@ -194,6 +194,16 @@ window.I18N_PAGE = {
de: "Wir haben dir einen 6-stelligen Code per E-Mail geschickt.", "de-CH": "Mir händ dir en 6-stellige Code per E-Mail gschickt.",
en: "We've sent you a 6-digit code by email.", fr: "Nous t'avons envoyé un code à 6 chiffres par e-mail.", pt: "Enviamos um código de 6 dígitos para o seu e-mail.",
},
/* Hinweis im Registrierungsformular (21.08.2026): erklärt vorab, dass jetzt
zwei weitere Schritte kommen (Code aus der E-Mail, danach eigener
Zugangscode) -- sonst wirkt der Sprung zur Code-Eingabe überraschend. */
ab_reg_hinweis_bestaetigung: {
de: "Wir schicken dir gleich einen 6-stelligen Code per E-Mail. Danach legst du deinen eigenen Zugangscode fest.",
"de-CH": "Mir schicked dir grad en 6-stellige Code per E-Mail. Denn legsch du din eigene Zuegangscode fescht.",
en: "We'll send you a 6-digit code by email in a moment. After that you'll set your own access code.",
fr: "Nous t'enverrons dans un instant un code à 6 chiffres par e-mail. Ensuite, tu définiras ton propre code d'accès.",
pt: "Enviaremos em instantes um código de 6 dígitos para o seu e-mail. Depois você define o seu próprio código de acesso.",
},
ab_feld_code: {
de: "Code", "de-CH": "Code",
en: "Code", fr: "Code", pt: "Código",
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Du bist Streamer und suchst einen Manager? Bewirb dich als Creator bei Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-creator.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-creator-mobile.jpg');">
<div id="site-header"></div>
@@ -70,8 +70,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821n"></script>
<script src="assets/js/i18n-bewerben-creator.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/forms.js?v=20260821o"></script>
<script src="assets/js/i18n-bewerben-creator.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -22,10 +22,10 @@
<meta name="twitter:description" content="Kooperationsanfrage an Manager DogFather / Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-mobile.jpg');">
<div id="site-header"></div>
@@ -68,8 +68,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821n"></script>
<script src="assets/js/i18n-bewerben-kooperation.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/forms.js?v=20260821o"></script>
<script src="assets/js/i18n-bewerben-kooperation.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Werde Modi bei Team Dogi — DogFathers persönliches Community-Team." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-modi.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-modi-mobile.jpg');">
<div id="site-header"></div>
@@ -79,8 +79,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821n"></script>
<script src="assets/js/i18n-bewerben-modi.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/forms.js?v=20260821o"></script>
<script src="assets/js/i18n-bewerben-modi.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Werde Scout für DogFather bei der Spicy Media Agentur — finde neue Talente." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-scout.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-scout-mobile.jpg');">
<div id="site-header"></div>
@@ -71,8 +71,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821n"></script>
<script src="assets/js/i18n-bewerben-scout.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/forms.js?v=20260821o"></script>
<script src="assets/js/i18n-bewerben-scout.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Werde Teil vom Team: Modi bei Team Dogi, Scout oder Creator bei Spicy Media, oder Kooperation anfragen." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-split.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-split-mobile.jpg');">
<div id="site-header"></div>
@@ -85,7 +85,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-bewerben.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-bewerben.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Der Husky an DogFathers Seite — das tierische Gesicht der HasiDog-Welt." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-casper.jpg');--page-bg-mobile:url('/assets/img/bg-casper-mobile.jpg');">
<div id="site-header"></div>
@@ -85,7 +85,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-casper.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-casper.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Team Dogi ist Familie — die Community rund um DogFather und HasiDog." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-community.jpg');--page-bg-mobile:url('/assets/img/bg-community-mobile.jpg');">
<div id="site-header"></div>
@@ -84,7 +84,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-community.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-community.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Content Creator, die Teil von DogFather / Spicy Media sind." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-creator.jpg');--page-bg-mobile:url('/assets/img/bg-creator-mobile.jpg');">
<div id="site-header"></div>
@@ -60,9 +60,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-creator.js?v=20260821n"></script>
<script src="assets/js/i18n-creator.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-creator.js?v=20260821o"></script>
<script src="assets/js/i18n-creator.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const grid = document.getElementById("creator-grid");
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Wer ist DogFather wirklich? Persönlichkeit, Werte und die Geschichte hinter dem Content." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-dogfather-person.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-person-mobile.jpg');">
<div id="site-header"></div>
@@ -158,7 +158,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-dogfather.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-dogfather.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+2 -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=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<style>
/* =====================================================================
Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="HasiDog: Maskottchen, Musikprojekt und die verspielte, familiäre Seite von Team Dogi." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-hasidog.jpg');--page-bg-mobile:url('/assets/img/bg-hasidog-mobile.jpg');">
<div id="site-header"></div>
@@ -186,7 +186,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-hasidog.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-hasidog.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+8 -8
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="DOGFATHER UNIVERSE: die Heimat von DogFather als Streamer, der HasiDog-Community und Manager DogFather bei Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="start-seite">
<div id="site-header"></div>
@@ -290,10 +290,10 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821n"></script>
<script src="assets/js/data-scouts.js?v=20260821n"></script>
<script src="assets/js/i18n-index.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-modis.js?v=20260821o"></script>
<script src="assets/js/data-scouts.js?v=20260821o"></script>
<script src="assets/js/i18n-index.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
// Gleiche URL wie in assets/js/forms.js / postfach.html — läuft seit
// 05.08.2026 auf dem eigenen Server statt auf Cloudflare Workers.
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Kontakt, Impressum und Datenschutz von DOGFATHER UNIVERSE." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body>
<div id="site-header"></div>
@@ -126,7 +126,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-kontakt.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-kontakt.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Alle Social-Media- und Musik-Links von DogFather an einem Ort — plus QR-Codes." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* Rabattcodes: an das Supporter-Abo gekoppelt (Nutzer-Feedback
03.08.2026: "verbinde es mit dem Abo... wenn die Leute drauf drücken
@@ -140,8 +140,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-links.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-links.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
(function () {
document.addEventListener("DOMContentLoaded", function () {
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="DogFather als Manager bei der Spicy Media Agency: Scouts, Creator und faire Zusammenarbeit." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-manager.jpg');--page-bg-mobile:url('/assets/img/bg-manager-mobile.jpg');">
<div id="site-header"></div>
@@ -139,9 +139,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-manager.js?v=20260821n"></script>
<script src="assets/js/i18n-manager.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-manager.js?v=20260821o"></script>
<script src="assets/js/i18n-manager.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
// "Weitere Manager" (21.08.2026) -- siehe Kommentar bei #weitere-manager-root
// oben. Aktuell IMMER window.MANAGERS === [] (siehe data-manager.js), Inhalt
+9 -9
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Fotos und Videos rund um Husky Casper an einem Ort." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" data-welt="casper" style="--page-bg:url('/assets/img/bg-medien-casper.jpg');--page-bg-mobile:url('/assets/img/bg-medien-casper-mobile.jpg');">
<div id="site-header"></div>
@@ -59,10 +59,10 @@
</div>
<div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821n"></script>
<script src="assets/js/data-videos.js?v=20260821n"></script>
<script src="assets/js/medien-welt.js?v=20260821n"></script>
<script src="assets/js/i18n-medien-casper.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-media.js?v=20260821o"></script>
<script src="assets/js/data-videos.js?v=20260821o"></script>
<script src="assets/js/medien-welt.js?v=20260821o"></script>
<script src="assets/js/i18n-medien-casper.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+10 -10
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Fotos, Videos und Events aus DogFathers Welt an einem Ort." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" data-welt="dogfather" style="--page-bg:url('/assets/img/bg-dogfather.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-mobile.jpg');">
<div id="site-header"></div>
@@ -65,11 +65,11 @@
</div>
<div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821n"></script>
<script src="assets/js/data-videos.js?v=20260821n"></script>
<script src="assets/js/data-aktionen.js?v=20260821n"></script>
<script src="assets/js/medien-welt.js?v=20260821n"></script>
<script src="assets/js/i18n-medien-dogfather.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-media.js?v=20260821o"></script>
<script src="assets/js/data-videos.js?v=20260821o"></script>
<script src="assets/js/data-aktionen.js?v=20260821o"></script>
<script src="assets/js/medien-welt.js?v=20260821o"></script>
<script src="assets/js/i18n-medien-dogfather.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+10 -10
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Fotos, Videos und Events aus der HasiDog-Welt an einem Ort." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" data-welt="hasidog" style="--page-bg:url('/assets/img/bg-medien-hasidog.jpg');--page-bg-mobile:url('/assets/img/bg-medien-hasidog-mobile.jpg');">
<div id="site-header"></div>
@@ -65,11 +65,11 @@
</div>
<div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821n"></script>
<script src="assets/js/data-videos.js?v=20260821n"></script>
<script src="assets/js/data-aktionen.js?v=20260821n"></script>
<script src="assets/js/medien-welt.js?v=20260821n"></script>
<script src="assets/js/i18n-medien-hasidog.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-media.js?v=20260821o"></script>
<script src="assets/js/data-videos.js?v=20260821o"></script>
<script src="assets/js/data-aktionen.js?v=20260821o"></script>
<script src="assets/js/medien-welt.js?v=20260821o"></script>
<script src="assets/js/i18n-medien-hasidog.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+10 -10
View File
@@ -16,10 +16,10 @@
<meta name="twitter:card" content="summary_large_image" />
<meta name="robots" content="noindex" /> <!-- generische URL, echte Team-Seiten verlinken hierher -->
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
.profil-photo { aspect-ratio: 4/5; max-width: 420px; margin: 0 auto; }
.profil-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
@@ -130,12 +130,12 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821n"></script>
<script src="assets/js/data-scouts.js?v=20260821n"></script>
<script src="assets/js/data-creator.js?v=20260821n"></script>
<script src="assets/js/data-manager.js?v=20260821n"></script>
<script src="assets/js/i18n-profil.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-modis.js?v=20260821o"></script>
<script src="assets/js/data-scouts.js?v=20260821o"></script>
<script src="assets/js/data-creator.js?v=20260821o"></script>
<script src="assets/js/data-manager.js?v=20260821o"></script>
<script src="assets/js/i18n-profil.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const slug = new URLSearchParams(location.search).get("p") || "";
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Rabattcodes von Dogis Partnern für Team Dogi — spar bares Geld bei ausgewählten Marken." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* Leere-Zustand-Kachel im "Gutschein"-Look: gestrichelter Rand wie ein
ausgeschnittener Coupon, damit auch OHNE echte Codes schon klar ist,
@@ -131,8 +131,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-rabattcodes.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-rabattcodes.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
(function () {
document.addEventListener("DOMContentLoaded", function () {
+53 -17
View File
@@ -72,54 +72,69 @@ function pruefeCode(code) {
Kontrolle geht eine Willkommens-E-Mail raus; schlägt deren Versand fehl,
ist das KEIN Grund die Registrierung scheitern zu lassen (das Konto ist
ja schon nutzbar) — sie wird nur im Protokoll vermerkt. */
/* Schritt 1 von 3 der Registrierung (Nutzer-Wunsch 21.08.2026: "bei der
ersten registrierung sollen die eine email bekommen mit einem einmaligen
code damit wir auch wissen dass email stimmt und danach wenn sie den code
eingegeben haben sollen die erst ihren zugangscode auswählen/eintippen
können").
Vorher wurde hier direkt `email_verified = 1` gesetzt UND der Zugangscode
im selben Schritt entgegengenommen -- die E-Mail-Adresse wurde also nie
geprüft. Man konnte sich mit einer fremden oder erfundenen Adresse
registrieren und kam sofort rein.
Jetzt legt dieser Schritt nur das UNBESTÄTIGTE Konto an (email_verified = 0,
noch KEIN Zugangscode) und verschickt einen Einmalcode. Weiter geht es über
verifySupporterEmail() (Schritt 2) und erst danach setSupporterAccessCode()
(Schritt 3). Es wird hier bewusst KEIN Session-Token zurückgegeben -- ohne
bestätigte E-Mail ist man nicht eingeloggt. */
export async function registerSupporter(req, res) {
const body = req.body || {};
const displayName = String(body.displayName || "").trim().slice(0, 60);
const tiktokUsername = normalizeTikTokUsername(body.tiktokUsername);
const email = normalizeEmail(body.email);
const accessCode = String(body.accessCode || "");
if (!displayName) return bad(res, "Bitte gib einen Namen oder Anzeigenamen an.");
if (!email || !email.includes("@")) return bad(res, "Bitte gib eine gültige E-Mail-Adresse an.");
const codeFehler = pruefeCode(accessCode);
if (codeFehler) return bad(res, codeFehler);
const existing = db.prepare(`SELECT id, access_code_hash FROM supporter_users WHERE email = ?`).get(email);
if (existing && existing.access_code_hash) {
return bad(res, "Für diese E-Mail-Adresse gibt es schon ein Konto. Bitte logg dich einfach ein.", 409);
}
const codeHash = await hashAccessCode(accessCode);
let supporterId;
if (existing) {
// Konto war angelegt, hatte aber noch keinen eigenen Code (z.B. ein
// abgebrochener Versuch aus der Zeit davor) -> jetzt vervollständigen.
// Konto existiert, ist aber noch unfertig (abgebrochener Versuch, oder
// Altkonto ohne eigenen Code) -> Angaben auffrischen, Bestätigung neu
// anstoßen. Kein Fehler nach außen: ein zweiter Anlauf soll einfach
// funktionieren, statt in einer Sackgasse zu enden.
supporterId = existing.id;
db.prepare(
`UPDATE supporter_users
SET display_name = ?, tiktok_username = ?, email_verified = 1,
access_code_hash = ?, access_code_set_at = ?
SET display_name = ?, tiktok_username = ?, email_verified = 0
WHERE id = ?`
).run(displayName, tiktokUsername, codeHash, nowIso(), supporterId);
).run(displayName, tiktokUsername, supporterId);
} else {
supporterId = generateId();
db.prepare(
`INSERT INTO supporter_users
(id, display_name, tiktok_username, email, email_verified, created_at, access_code_hash, access_code_set_at, last_login_at)
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)`
).run(supporterId, displayName, tiktokUsername, email, nowIso(), codeHash, nowIso(), nowIso());
(id, display_name, tiktok_username, email, email_verified, created_at)
VALUES (?, ?, ?, ?, 0, ?)`
).run(supporterId, displayName, tiktokUsername, email, nowIso());
logAction("system", "supporter.registered", supporterId, { email });
}
// Willkommens-E-Mail: nice-to-have, kein Blocker (siehe Kommentar oben).
// Anders als die Willkommens-E-Mail ist DIESE Mail ein echter Blocker:
// ohne den Code kommt niemand weiter. Schlägt der Versand fehl, muss das
// sichtbar scheitern statt so zu tun, als sei alles in Ordnung.
const code = await issueAuthCode(supporterId, "verify_email");
try {
await sendWelcomeMail({ to: email, name: displayName });
await sendVerifyEmailCode({ to: email, name: displayName, code });
} catch (err) {
logAction("system", "supporter.welcome_mail_failed", supporterId, { fehler: String(err?.message || err).slice(0, 200) });
return emailErrorResponse(res, err);
}
const token = await createSupporterSession(supporterId);
return json(res, { ok: true, token });
return json(res, { ok: true });
}
/* Einloggen mit E-Mail + selbst gewähltem Code — der neue Normalweg.
@@ -177,6 +192,17 @@ export async function setSupporterAccessCode(req, res, supporter) {
const fehler = pruefeCode(neuerCode);
if (fehler) return bad(res, fehler);
// Zusätzliche Absicherung (21.08.2026): Ein Zugangscode darf NUR gesetzt
// werden, wenn die E-Mail-Adresse bestätigt ist. Aktuell kann ohne
// Bestätigung ohnehin keine Session entstehen (registerSupporter gibt kein
// Token mehr zurück) -- diese Prüfung hängt aber nicht davon ab, dass das
// so bleibt. Ohne sie wäre der Bestätigungsschritt nur im Formular
// versteckt und ließe sich mit einem direkten Aufruf überspringen.
const stand = db.prepare(`SELECT email_verified FROM supporter_users WHERE id = ?`).get(supporter.id);
if (!stand || !stand.email_verified) {
return bad(res, "Bitte bestätige zuerst deine E-Mail-Adresse.", 403);
}
const codeHash = await hashAccessCode(neuerCode);
db.prepare(`UPDATE supporter_users SET access_code_hash = ?, access_code_set_at = ? WHERE id = ?`).run(codeHash, nowIso(), supporter.id);
clearAttempts(`supporter:${supporter.email}`);
@@ -199,6 +225,16 @@ export async function verifySupporterEmail(req, res) {
db.prepare(`UPDATE supporter_users SET email_verified = 1, last_login_at = ? WHERE id = ?`).run(nowIso(), supporter.id);
logAction("system", "supporter.email_verified", supporter.id, null);
// Willkommens-E-Mail erst hier (21.08.2026) -- vorher ging sie schon beim
// Registrieren raus, also an eine noch ungeprüfte Adresse. Jetzt steht
// fest, dass die Adresse wirklich der Person gehört. Weiterhin
// nice-to-have: schlägt sie fehl, blockiert das die Anmeldung nicht.
try {
await sendWelcomeMail({ to: email, name: supporter.display_name });
} catch (err) {
logAction("system", "supporter.welcome_mail_failed", supporter.id, { fehler: String(err?.message || err).slice(0, 200) });
}
const token = await createSupporterSession(supporter.id);
return json(res, { ok: true, token });
}
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Was die Community und das Team über DogFather sagen." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-stimmen.jpg');--page-bg-mobile:url('/assets/img/bg-stimmen-mobile.jpg');">
<div id="site-header"></div>
@@ -87,8 +87,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-stimmen.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/stimmen.js?v=20260821n"></script>
<script src="assets/js/i18n-stimmen.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script src="assets/js/stimmen.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Die persönliche Geschichte hinter DogFather — Filipe, Streamer, Manager bei Spicy Media und Hundepapa von Husky Casper." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamer.jpg');--page-bg-mobile:url('/assets/img/bg-streamer-mobile.jpg');">
<div id="site-header"></div>
@@ -217,7 +217,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-streamer.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-streamer.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+9 -9
View File
@@ -19,17 +19,17 @@
<meta name="twitter:description" content="Wann geht DogFather live? Streamplan und Livebereich." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821n');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821n');">
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821o');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821o');">
<div id="site-header"></div>
<main>
<section class="hero page-bg">
<div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821n" alt="" loading="eager" /></div>
<div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821o" alt="" loading="eager" /></div>
<div class="container">
<span class="eyebrow" data-i18n="sp_eyebrow">Welt 1 — DogFather</span>
<h1 data-i18n="sp_h1">Streamplan &amp; Livebereich</h1>
@@ -126,8 +126,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-streamplan.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/streamplan.js?v=20260821n"></script>
<script src="assets/js/i18n-streamplan.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script src="assets/js/streamplan.js?v=20260821o"></script>
</body>
</html>
+7 -7
View File
@@ -9,10 +9,10 @@
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0b0d10" />
<meta name="robots" content="noindex" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* =====================================================================
Supporter-Bereich — komplett neu gestaltet (Nutzer-Feedback
@@ -515,9 +515,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-supporter.js?v=20260821n"></script>
<script src="assets/js/supporter.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-supporter.js?v=20260821o"></script>
<script src="assets/js/supporter.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
(function () {
const S = window.DogiSupporter;
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="VanVan, Diene, Funny, Miss und Marina — das Modi-/Core-Team von Team Dogi." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-modis.jpg');--page-bg-mobile:url('/assets/img/bg-modis.jpg');">
<div id="site-header"></div>
@@ -72,9 +72,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821n"></script>
<script src="assets/js/i18n-team-modis.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-modis.js?v=20260821o"></script>
<script src="assets/js/i18n-team-modis.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const grid = document.getElementById("modis-grid");
+7 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Die Scouts von Team Dogi bei Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-scouts.jpg');--page-bg-mobile:url('/assets/img/bg-scouts-mobile.jpg');">
<div id="site-header"></div>
@@ -49,9 +49,9 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/data-scouts.js?v=20260821n"></script>
<script src="assets/js/i18n-team-scouts.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/data-scouts.js?v=20260821o"></script>
<script src="assets/js/i18n-team-scouts.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const grid = document.getElementById("scouts-grid");
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Eine persönliche Widmung von Dogi an Tili — seine TikTok-Muddi, die erste und bis heute einzige Streamerin, die ihm all die Zeit treu geblieben ist." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* Gleiche Anpassung wie vanvan.html: der site-weit etwas größere
Fließtext wirkt bei einem so langen persönlichen Brief zu wuchtig —
@@ -153,7 +153,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-tt-muddi.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-tt-muddi.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="VanVan erzählt ihre Geschichte: wie sie zu Team Dogi kam, wer sie ist und was DogFather und Team Dogi ihr bedeuten." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* Auf Wunsch: der site-weit vergrößerte Fließtext wirkte auf VanVans
eigener Seite zu groß (viel Text am Stück) — hier gezielt wieder
@@ -175,7 +175,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-vanvan.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-vanvan.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+10 -10
View File
@@ -19,10 +19,10 @@
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-admin.png" />
<meta name="apple-mobile-web-app-title" content="DogiCrew-Verwaltung" />
<meta name="theme-color" content="#0e0a16" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
<style>
/* =====================================================================
verwaltung.html — Dogis privater Command-Center-Bereich.
@@ -1207,12 +1207,12 @@
<!-- 21.08.2026: für den "Bestehende Mitglieder importieren"-Knopf in der
Team-Verwaltung -- liest die von Hand gepflegten Profile aus diesen
Dateien aus und bietet an, sie 1:1 in die Datenbank zu übernehmen. -->
<script src="assets/js/data-modis.js?v=20260821n"></script>
<script src="assets/js/data-scouts.js?v=20260821n"></script>
<script src="assets/js/data-creator.js?v=20260821n"></script>
<script src="assets/js/data-manager.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/admin-auth.js?v=20260821n"></script>
<script src="assets/js/data-modis.js?v=20260821o"></script>
<script src="assets/js/data-scouts.js?v=20260821o"></script>
<script src="assets/js/data-creator.js?v=20260821o"></script>
<script src="assets/js/data-manager.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script src="assets/js/admin-auth.js?v=20260821o"></script>
<script>
(function () {
const API_BASE_URL = "https://postfach.dogfather-universe.com";
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Die Werte, nach denen DogFather als Manager bei Spicy Media arbeitet." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-werte.jpg');--page-bg-mobile:url('/assets/img/bg-werte-mobile.jpg');">
<div id="site-header"></div>
@@ -113,7 +113,7 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-werte.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-werte.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
</body>
</html>
+6 -6
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Die Geschichte von DogFather in einer interaktiven Zeitleiste." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821n" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821o" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821o" />
</head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-zeitreise.jpg');--page-bg-mobile:url('/assets/img/bg-zeitreise-mobile.jpg');">
<div id="site-header"></div>
@@ -237,8 +237,8 @@
</main>
<div id="site-footer"></div>
<script src="assets/js/i18n-zeitreise.js?v=20260821n"></script>
<script src="assets/js/main.js?v=20260821n"></script>
<script src="assets/js/i18n-zeitreise.js?v=20260821o"></script>
<script src="assets/js/main.js?v=20260821o"></script>
<script>
/* Wachstums-Reveal: Äste blenden sich beim Scrollen sanft ein — passt
zum "wachsenden Baum"-Konzept dieser Seite. Rein page-lokal, betrifft