Compare commits

...
2 Commits
Author SHA1 Message Date
DogFatherGitandClaude Opus 5 6910112d97 Stimmen: nur noch die zwei Originalfiguren + eigenes Bild hochladen
Nutzer-Wunsch 21.08.2026: "benutz da bitte nur die originalen husky und
hasen. mach nur die zwei. und die auswahl wo die leute selbst ein bild
rein setzen können."

- Auswahl von acht auf zwei reduziert (DogFather-Husky, HasiDog). Die
  übrigen Bilddateien bleiben liegen, falls sie je zurücksollen -- es
  genügt, die Zeile in data-stimmen-avatare.js und die Id in
  ERLAUBTE_AVATARE wieder zu ergänzen.
- Neue Kachel "eigenes Bild" (gestrichelter Rand + Plus), die den
  Dateidialog öffnet, das Bild sofort hochlädt und als Vorschau in der
  Kachel zeigt.

Bereits freigegebene Stimmen mit einer der entfernten Figuren zeigen
wieder den Anfangsbuchstaben statt eines kaputten Bildes -- die
Auflösung unbekannter Ids liefert null, das war schon so vorgesehen.

Sicherheit des öffentlichen Uploads (bisher war Hochladen bewusst nur
der Verwaltung erlaubt):
- Gleiche multer-Härtung wie der Verwaltungs-Upload: nur JPG/PNG/WebP,
  max. 5 MB, zufälliger UUID-Dateiname (kein Originalname).
- Der Server nimmt im Avatar-Feld weiterhin NUR bekannte Ids an oder
  eine Adresse, die exakt auf den eigenen Upload-Ordner zeigt und danach
  nur aus UUID + Bildendung besteht. Gegengetestet: fremde Domains,
  "../"-Ausbruch, .svg/.html, javascript:, angehängte Skripte und http
  statt https werden alle abgelehnt.
- Missbrauchsbremse gegen Vollschreiben der Festplatte: max. 10 Uploads
  pro Stunde und IP.
- Sichtbar wird ein Bild ohnehin erst, wenn die Stimme freigegeben wird.

Mit Playwright end-to-end geprüft (10 Tests) plus 9 Sicherheitsfälle.

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

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-21 18:23:17 +02:00
DogFatherGitandClaude Opus 5 e2bd436084 Arbeit der parallelen Session gesichert (Profilbilder für Stimmen, Diene-Zugang)
Wie beim vorherigen Mal lag das nur als Arbeitskopie auf dem Server, nicht
in git. Unverändert übernommen, bevor darauf aufgebaut wird:
- Profilbild-Auswahl für die Stimmen (data-stimmen-avatare.js neu,
  stimmen.js, i18n-stimmen.js, stimmen.html, verwaltung.html, main.css)
- Dritte Zugangs-Kachel für Diene (gate.html, server/gate.js)

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-21 18:17:47 +02:00
42 changed files with 699 additions and 249 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="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>Seite nicht gefunden — DOGFATHER UNIVERSE</title> <title>Seite nicht gefunden — DOGFATHER UNIVERSE</title>
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body> <body>
<div id="site-header"></div> <div id="site-header"></div>
@@ -41,7 +41,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-404.js?v=20260821p"></script> <script src="assets/js/i18n-404.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+7 -7
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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* ===================================================================== /* =====================================================================
Preis-/Anmelde-Kiste — komplett neu gestaltet (Nutzer-Feedback Preis-/Anmelde-Kiste — komplett neu gestaltet (Nutzer-Feedback
@@ -603,9 +603,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-abonnieren.js?v=20260821p"></script> <script src="assets/js/i18n-abonnieren.js?v=20260821q"></script>
<script src="assets/js/supporter.js?v=20260821p"></script> <script src="assets/js/supporter.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
(function () { (function () {
const S = window.DogiSupporter; const S = window.DogiSupporter;
+5 -5
View File
@@ -20,10 +20,10 @@
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body> <body>
<div id="site-header"></div> <div id="site-header"></div>
@@ -134,6 +134,6 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body> <body>
<div id="site-header"></div> <div id="site-header"></div>
@@ -51,9 +51,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-aktionen.js?v=20260821p"></script> <script src="assets/js/data-aktionen.js?v=20260821q"></script>
<script src="assets/js/i18n-aktion-detail.js?v=20260821p"></script> <script src="assets/js/i18n-aktion-detail.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const params = new URLSearchParams(location.search); const params = new URLSearchParams(location.search);
+69
View File
@@ -4107,6 +4107,75 @@ body.start-seite .music-toggle:focus-visible {
background: linear-gradient(135deg, #ffd0e8, #a8e4f2); background: linear-gradient(135deg, #ffd0e8, #a8e4f2);
box-shadow: 0 0 16px rgba(255, 154, 208, .3); box-shadow: 0 0 16px rgba(255, 154, 208, .3);
} }
/* Gewähltes Profilbild statt Anfangsbuchstabe (21.08.2026). Erbt Größe und
runde Form von .stimmen-karte-avatar oben, ersetzt nur Füllung/Farbe. */
.stimmen-karte-avatar-bild {
padding: 0;
overflow: hidden;
background: rgba(255, 255, 255, .05);
box-shadow: 0 0 14px rgba(212, 175, 55, .25);
}
.stimmen-karte-avatar-bild img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ---------- Profilbild-Auswahl im Einreichen-Formular (21.08.2026) ----------
Nutzer-Wunsch: "paar geile bilder zum aussuchen ... das soll denen auch
als möglichkeit gegeben sein". Kachelraster, das sich der Breite anpasst;
die getroffene Wahl bekommt einen goldenen Ring statt nur einer leichten
Aufhellung — sonst ist auf dunklem Grund kaum erkennbar, was gewählt ist. */
.stimmen-avatar-titel {
display: block;
font-size: .82rem; font-weight: 700; letter-spacing: .04em;
text-transform: uppercase; color: var(--text-muted);
margin-bottom: .25rem;
}
.stimmen-avatar-hinweis { margin: 0 0 .7rem; }
.stimmen-avatar-wahl {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
gap: .6rem;
}
.stimmen-avatar-kachel {
position: relative;
aspect-ratio: 1;
padding: 0;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
background: rgba(255, 255, 255, .04);
border: 2px solid rgba(255, 255, 255, .12);
transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.stimmen-avatar-kachel img { width: 100%; height: 100%; object-fit: cover; display: block; }
.stimmen-avatar-keins {
display: flex; align-items: center; justify-content: center;
width: 100%; height: 100%;
font-size: 1.15rem; color: var(--text-muted);
}
.stimmen-avatar-kachel:hover { transform: translateY(-2px); border-color: rgba(255, 233, 168, .6); }
.stimmen-avatar-kachel.aktiv {
border-color: #ffe9a8;
box-shadow: 0 0 0 2px rgba(212, 175, 55, .45), 0 0 18px rgba(212, 175, 55, .4);
}
.stimmen-avatar-kachel:focus-visible { outline: 2px solid #ffe9a8; outline-offset: 3px; }
/* "Eigenes Bild"-Kachel (21.08.2026): gestrichelter Rand, damit sofort
erkennbar ist, dass hier noch nichts drin ist und etwas hinzugefügt
werden kann -- im Unterschied zu den fertigen Auswahlbildern daneben.
Sobald ein Bild hochgeladen wurde, ersetzt das <img> das Plus-Zeichen
und die Kachel sieht aus wie die anderen. */
.stimmen-avatar-eigenes { border-style: dashed; border-color: rgba(255, 233, 168, .45); }
.stimmen-avatar-eigenes:has(img) { border-style: solid; }
.stimmen-avatar-plus {
display: flex; align-items: center; justify-content: center;
width: 100%; height: 100%;
font-size: 1.3rem; line-height: 1; color: #ffe9a8;
}
/* Während des Hochladens: kurz gedimmt, damit klar ist, dass etwas läuft. */
.stimmen-avatar-eigenes.laedt { opacity: .5; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
.stimmen-avatar-kachel { transition: none; }
.stimmen-avatar-kachel:hover { transform: none; }
}
.stimmen-karte-namen { display: flex; flex-direction: column; gap: .1rem; min-width: 0; } .stimmen-karte-namen { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.stimmen-karte-name { font-weight: 700; color: var(--text); font-size: .95rem; } .stimmen-karte-name { font-weight: 700; color: var(--text); font-size: .95rem; }
.stimmen-karte-tiktok { .stimmen-karte-tiktok {
+44
View File
@@ -0,0 +1,44 @@
/* =====================================================================
data-stimmen-avatare.js — Auswahlbilder für die "Stimmen" (21.08.2026,
Nutzer-Wunsch: "ich soll auch paar geile bilder zum aussuchen haben als
profilbild falls die leute kein bild mit schicken, das soll denen auch
als möglichkeit gegeben sein").
EINE gemeinsame Liste für beide Seiten: stimmen.html (dort wählt die
einreichende Person selbst) und verwaltung.html (dort kann Dogi/VanVan
das Bild nachträglich ändern). Getrennte Listen wären auseinander-
gelaufen, sobald irgendwann ein Bild dazukommt oder wegfällt.
Bewusst nur Figuren, Maskottchen und das Logo — KEINE echten Personen
aus dem Team. Sonst könnte sich eine fremde Person mit dem Gesicht von
Dogi, VanVan oder einem Modi als Absender darstellen.
WICHTIG: Die "id"-Werte müssen mit der Liste
ERLAUBTE_AVATARE in server-internal/routes/testimonials.js
übereinstimmen — der Server nimmt nur bekannte Ids an, damit über das
öffentliche Formular kein beliebiger Pfad eingeschleust werden kann.
===================================================================== */
/* Nutzer-Wunsch 21.08.2026: "benutz da bitte nur die originalen husky und
hasen. mach nur die zwei." -- vorher standen hier acht Bilder (zusätzlich
Husky-Kopf, zwei Kosmos-Varianten, zwei Casper-Bilder, Logo). Die beiden
Originalfiguren bleiben, der Rest ist raus. Die Bilddateien selbst wurden
NICHT gelöscht: sollten sie je wieder gebraucht werden, genügt es, die
Zeile hier (und die passende Id in ERLAUBTE_AVATARE, siehe unten) wieder
zu ergänzen. */
window.STIMMEN_AVATARE = [
{ id: "husky-hoodie", name: "DogFather-Husky", datei: "/assets/img/stimmen-avatare/stimme-husky-hoodie.png" },
{ id: "hase-hoodie", name: "HasiDog", datei: "/assets/img/stimmen-avatare/stimme-hase-hoodie.png" },
];
/* Liefert die Bilddatei zu einer gespeicherten Auswahl.
- bekannte Id -> das zugehörige Auswahlbild
- "/uploads/…" -> ein von der Verwaltung hochgeladenes eigenes Foto
(kommt als vollständige Adresse vom Server, siehe testimonials.js)
- sonst -> null, dann zeigt die Karte wie bisher den Anfangs-
buchstaben des Namens. */
window.stimmeAvatarBild = function (wert) {
if (!wert) return null;
if (/^https?:\/\//i.test(wert)) return wert;
const treffer = (window.STIMMEN_AVATARE || []).find((a) => a.id === wert);
return treffer ? treffer.datei : null;
};
+11
View File
@@ -40,6 +40,17 @@ window.I18N_PAGE = {
de: "Deine Nachricht", "de-CH": "Dini Nachricht", de: "Deine Nachricht", "de-CH": "Dini Nachricht",
en: "Your Message", fr: "Ton message", pt: "Sua mensagem", en: "Your Message", fr: "Ton message", pt: "Sua mensagem",
}, },
st_form_avatar_label: {
de: "Dein Profilbild (optional)", "de-CH": "Dis Profilbild (optional)",
en: "Your profile picture (optional)", fr: "Ta photo de profil (facultatif)", pt: "Sua foto de perfil (opcional)",
},
st_form_avatar_hinweis: {
de: "Ohne Auswahl erscheint einfach der erste Buchstabe deines Namens.",
"de-CH": "Ohni Uuswahl erschiint eifach de erscht Buechstab vo dim Name.",
en: "If you don't pick one, the first letter of your name is shown instead.",
fr: "Sans choix, la première lettre de ton prénom s'affichera.",
pt: "Sem escolha, aparece apenas a primeira letra do seu nome.",
},
st_form_nachricht_placeholder: { st_form_nachricht_placeholder: {
de: "Erzähl uns, was dir Team Dogi bedeutet …", "de-CH": "Verzell eus, was dir Team Dogi bedütet …", de: "Erzähl uns, was dir Team Dogi bedeutet …", "de-CH": "Verzell eus, was dir Team Dogi bedütet …",
en: "Tell us what Team Dogi means to you …", fr: "Dis-nous ce que Team Dogi représente pour toi …", pt: "Conte-nos o que a Team Dogi significa para você …", en: "Tell us what Team Dogi means to you …", fr: "Dis-nous ce que Team Dogi représente pour toi …", pt: "Conte-nos o que a Team Dogi significa para você …",
+93 -2
View File
@@ -9,6 +9,88 @@
(function () { (function () {
const API_BASE_URL = "https://postfach.dogfather-universe.com"; const API_BASE_URL = "https://postfach.dogfather-universe.com";
/* ---------- Profilbild-Auswahl (21.08.2026) ----------
Baut die Kacheln aus der gemeinsamen Liste in data-stimmen-avatare.js
auf. Bewusst echte <button>-Elemente statt <div>: so ist die Auswahl
auch mit der Tastatur bedienbar und Screenreader melden sie als
Schaltfläche. "" = bewusst kein Bild (erste Kachel). */
let gewaehlterAvatar = "";
const wahlEl = document.getElementById("stimmen-avatar-wahl");
if (wahlEl) {
const liste = window.STIMMEN_AVATARE || [];
const kachel = (id, inhalt, titel) =>
`<button type="button" class="stimmen-avatar-kachel" data-avatar="${id}" role="radio" aria-checked="false" title="${titel}" aria-label="${titel}">${inhalt}</button>`;
/* Letzte Kachel: eigenes Bild (21.08.2026, Nutzer-Wunsch "und die
auswahl wo die leute selbst ein bild rein setzen können"). Die
versteckte Datei-Auswahl liegt gleich daneben; die Kachel selbst
löst sie nur aus, damit die Optik zu den anderen Kacheln passt. */
wahlEl.innerHTML =
kachel("", `<span class="stimmen-avatar-keins" aria-hidden="true">✦</span>`, "Kein Bild") +
liste.map((a) => kachel(a.id, `<img src="${a.datei}" alt="" loading="lazy" />`, a.name)).join("") +
`<button type="button" class="stimmen-avatar-kachel stimmen-avatar-eigenes" id="stimmen-avatar-eigenes"
role="radio" aria-checked="false" title="Eigenes Bild hochladen" aria-label="Eigenes Bild hochladen">
<span class="stimmen-avatar-plus" aria-hidden="true">+</span>
</button>
<input type="file" id="stimmen-avatar-datei" accept="image/jpeg,image/png,image/webp" hidden />`;
const eigenesBtn = document.getElementById("stimmen-avatar-eigenes");
const dateiInput = document.getElementById("stimmen-avatar-datei");
let eigenesBildUrl = "";
const markiere = () => {
wahlEl.querySelectorAll(".stimmen-avatar-kachel").forEach((b) => {
// Die "eigenes Bild"-Kachel hat kein data-avatar; sie gilt als
// gewählt, wenn die aktuelle Auswahl die hochgeladene Adresse ist.
const wert = b === eigenesBtn ? eigenesBildUrl : b.dataset.avatar;
const aktiv = !!gewaehlterAvatar === !!wert && wert === gewaehlterAvatar;
b.classList.toggle("aktiv", aktiv);
b.setAttribute("aria-checked", aktiv ? "true" : "false");
});
};
wahlEl.addEventListener("click", (e) => {
const btn = e.target.closest(".stimmen-avatar-kachel");
if (!btn) return;
if (btn === eigenesBtn) {
// Schon hochgeladen -> einfach wieder auswählen, sonst Dateidialog.
if (eigenesBildUrl) { gewaehlterAvatar = eigenesBildUrl; markiere(); }
else dateiInput.click();
return;
}
gewaehlterAvatar = btn.dataset.avatar;
markiere();
});
dateiInput.addEventListener("change", async () => {
const datei = dateiInput.files && dateiInput.files[0];
if (!datei) return;
const fehlerEl = document.getElementById("stimmen-fehler");
eigenesBtn.classList.add("laedt");
try {
const form = new FormData();
form.append("image", datei);
const res = await fetch(`${API_BASE_URL}/testimonials/upload-avatar`, { method: "POST", body: form });
const data = await res.json();
if (!data.ok) {
if (fehlerEl) { fehlerEl.textContent = data.error || "Das Bild konnte nicht hochgeladen werden."; fehlerEl.classList.add("zeige"); }
return;
}
eigenesBildUrl = data.url;
gewaehlterAvatar = data.url;
eigenesBtn.innerHTML = `<img src="${data.url}" alt="" />`;
markiere();
} catch {
if (fehlerEl) { fehlerEl.textContent = "Das Bild konnte nicht hochgeladen werden (keine Verbindung)."; fehlerEl.classList.add("zeige"); }
} finally {
eigenesBtn.classList.remove("laedt");
dateiInput.value = ""; // erlaubt, dieselbe Datei erneut zu wählen
}
});
markiere();
}
const formular = document.getElementById("stimmen-formular"); const formular = document.getElementById("stimmen-formular");
if (formular) { if (formular) {
const erfolgEl = document.getElementById("stimmen-erfolg"); const erfolgEl = document.getElementById("stimmen-erfolg");
@@ -28,7 +110,7 @@
const res = await fetch(`${API_BASE_URL}/testimonials/submit`, { const res = await fetch(`${API_BASE_URL}/testimonials/submit`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, tiktokUsername, nachricht }), body: JSON.stringify({ name, tiktokUsername, nachricht, avatar: gewaehlterAvatar || null }),
}); });
const data = await res.json(); const data = await res.json();
if (!data.ok) { if (!data.ok) {
@@ -74,7 +156,16 @@
(t) => `<div class="stimmen-karte"> (t) => `<div class="stimmen-karte">
<p class="stimmen-karte-text">${escapeHtml(t.nachricht)}</p> <p class="stimmen-karte-text">${escapeHtml(t.nachricht)}</p>
<div class="stimmen-karte-fuss"> <div class="stimmen-karte-fuss">
<span class="stimmen-karte-avatar" aria-hidden="true">${escapeHtml(erstesZeichen(t.name))}</span> ${
/* Gewähltes Bild, sonst wie bisher der Anfangsbuchstabe.
escapeHtml auch auf der Bildadresse: sie stammt zwar aus
unserer eigenen Liste bzw. vom Server, aber sie landet
hier in einem HTML-Attribut — da gehört sie grundsätzlich
entschärft, unabhängig von der Herkunft. */
(window.stimmeAvatarBild && window.stimmeAvatarBild(t.avatar))
? `<span class="stimmen-karte-avatar stimmen-karte-avatar-bild" aria-hidden="true"><img src="${escapeHtml(window.stimmeAvatarBild(t.avatar))}" alt="" loading="lazy" /></span>`
: `<span class="stimmen-karte-avatar" aria-hidden="true">${escapeHtml(erstesZeichen(t.name))}</span>`
}
<span class="stimmen-karte-namen"> <span class="stimmen-karte-namen">
<span class="stimmen-karte-name">${escapeHtml(t.name)}</span> <span class="stimmen-karte-name">${escapeHtml(t.name)}</span>
${t.tiktokUsername ? `<span class="stimmen-karte-tiktok">@${escapeHtml(t.tiktokUsername)}</span>` : ""} ${t.tiktokUsername ? `<span class="stimmen-karte-tiktok">@${escapeHtml(t.tiktokUsername)}</span>` : ""}
+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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -70,8 +70,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821p"></script> <script src="assets/js/forms.js?v=20260821q"></script>
<script src="assets/js/i18n-bewerben-creator.js?v=20260821p"></script> <script src="assets/js/i18n-bewerben-creator.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+7 -7
View File
@@ -22,10 +22,10 @@
<meta name="twitter:description" content="Kooperationsanfrage an Manager DogFather / Spicy Media." /> <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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -68,8 +68,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821p"></script> <script src="assets/js/forms.js?v=20260821q"></script>
<script src="assets/js/i18n-bewerben-kooperation.js?v=20260821p"></script> <script src="assets/js/i18n-bewerben-kooperation.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -79,8 +79,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821p"></script> <script src="assets/js/forms.js?v=20260821q"></script>
<script src="assets/js/i18n-bewerben-modi.js?v=20260821p"></script> <script src="assets/js/i18n-bewerben-modi.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -71,8 +71,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/forms.js?v=20260821p"></script> <script src="assets/js/forms.js?v=20260821q"></script>
<script src="assets/js/i18n-bewerben-scout.js?v=20260821p"></script> <script src="assets/js/i18n-bewerben-scout.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -85,7 +85,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-bewerben.js?v=20260821p"></script> <script src="assets/js/i18n-bewerben.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-casper.jpg');--page-bg-mobile:url('/assets/img/bg-casper-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -85,7 +85,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-casper.js?v=20260821p"></script> <script src="assets/js/i18n-casper.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-community.jpg');--page-bg-mobile:url('/assets/img/bg-community-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -84,7 +84,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-community.js?v=20260821p"></script> <script src="assets/js/i18n-community.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-creator.jpg');--page-bg-mobile:url('/assets/img/bg-creator-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -60,9 +60,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-creator.js?v=20260821p"></script> <script src="assets/js/data-creator.js?v=20260821q"></script>
<script src="assets/js/i18n-creator.js?v=20260821p"></script> <script src="assets/js/i18n-creator.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
const grid = document.getElementById("creator-grid"); 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -158,7 +158,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-dogfather.js?v=20260821p"></script> <script src="assets/js/i18n-dogfather.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+32 -2
View File
@@ -26,8 +26,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" /> <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-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="DogFather" /> <meta name="apple-mobile-web-app-title" content="DogFather" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<style> <style>
/* ===================================================================== /* =====================================================================
Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern
@@ -222,6 +222,25 @@
background: linear-gradient(120deg, #ff8fc6 0%, #c084fc 30%, #ffd76a 55%, #ff8fc6 80%, #c084fc 100%); background: linear-gradient(120deg, #ff8fc6 0%, #c084fc 30%, #ffd76a 55%, #ff8fc6 80%, #c084fc 100%);
background-size: 340% 340%; 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'); }
.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; } .gate-card .gate-fehler { margin-top: .7rem; }
@media (max-width: 480px) { @media (max-width: 480px) {
.gate-cards { flex-direction: column; align-items: center; } .gate-cards { flex-direction: column; align-items: center; }
@@ -275,6 +294,16 @@
</button> </button>
<p class="gate-fehler" id="gate-fehler-vanvan"></p> <p class="gate-fehler" id="gate-fehler-vanvan"></p>
</form> </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>
</div> </div>
@@ -374,6 +403,7 @@
} }
bindeGateKachel("gate-form-dogi", "gate-code-dogi", "gate-fehler-dogi"); bindeGateKachel("gate-form-dogi", "gate-code-dogi", "gate-fehler-dogi");
bindeGateKachel("gate-form-vanvan", "gate-code-vanvan", "gate-fehler-vanvan"); bindeGateKachel("gate-form-vanvan", "gate-code-vanvan", "gate-fehler-vanvan");
bindeGateKachel("gate-form-diene", "gate-code-diene", "gate-fehler-diene");
/* Service-Worker-Registrierung — auf allen anderen Seiten macht das main.js, diese Seite /* 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 ist aber bewusst eigenstaendig und laedt main.js nicht. Ohne registrierten Service
+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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-hasidog.jpg');--page-bg-mobile:url('/assets/img/bg-hasidog-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -186,7 +186,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-hasidog.js?v=20260821p"></script> <script src="assets/js/i18n-hasidog.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="start-seite"> <body class="start-seite">
<div id="site-header"></div> <div id="site-header"></div>
@@ -290,10 +290,10 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821p"></script> <script src="assets/js/data-modis.js?v=20260821q"></script>
<script src="assets/js/data-scouts.js?v=20260821p"></script> <script src="assets/js/data-scouts.js?v=20260821q"></script>
<script src="assets/js/i18n-index.js?v=20260821p"></script> <script src="assets/js/i18n-index.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
// Gleiche URL wie in assets/js/forms.js / postfach.html — läuft seit // Gleiche URL wie in assets/js/forms.js / postfach.html — läuft seit
// 05.08.2026 auf dem eigenen Server statt auf Cloudflare Workers. // 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:description" content="Kontakt, Impressum und Datenschutz von DOGFATHER UNIVERSE." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body> <body>
<div id="site-header"></div> <div id="site-header"></div>
@@ -126,7 +126,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-kontakt.js?v=20260821p"></script> <script src="assets/js/i18n-kontakt.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* Rabattcodes: an das Supporter-Abo gekoppelt (Nutzer-Feedback /* Rabattcodes: an das Supporter-Abo gekoppelt (Nutzer-Feedback
03.08.2026: "verbinde es mit dem Abo... wenn die Leute drauf drücken 03.08.2026: "verbinde es mit dem Abo... wenn die Leute drauf drücken
@@ -140,8 +140,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-links.js?v=20260821p"></script> <script src="assets/js/i18n-links.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
(function () { (function () {
document.addEventListener("DOMContentLoaded", 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-manager.jpg');--page-bg-mobile:url('/assets/img/bg-manager-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -139,9 +139,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-manager.js?v=20260821p"></script> <script src="assets/js/data-manager.js?v=20260821q"></script>
<script src="assets/js/i18n-manager.js?v=20260821p"></script> <script src="assets/js/i18n-manager.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
// "Weitere Manager" (21.08.2026) -- siehe Kommentar bei #weitere-manager-root // "Weitere Manager" (21.08.2026) -- siehe Kommentar bei #weitere-manager-root
// oben. Aktuell IMMER window.MANAGERS === [] (siehe data-manager.js), Inhalt // 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -59,10 +59,10 @@
</div> </div>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821p"></script> <script src="assets/js/data-media.js?v=20260821q"></script>
<script src="assets/js/data-videos.js?v=20260821p"></script> <script src="assets/js/data-videos.js?v=20260821q"></script>
<script src="assets/js/medien-welt.js?v=20260821p"></script> <script src="assets/js/medien-welt.js?v=20260821q"></script>
<script src="assets/js/i18n-medien-casper.js?v=20260821p"></script> <script src="assets/js/i18n-medien-casper.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -65,11 +65,11 @@
</div> </div>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821p"></script> <script src="assets/js/data-media.js?v=20260821q"></script>
<script src="assets/js/data-videos.js?v=20260821p"></script> <script src="assets/js/data-videos.js?v=20260821q"></script>
<script src="assets/js/data-aktionen.js?v=20260821p"></script> <script src="assets/js/data-aktionen.js?v=20260821q"></script>
<script src="assets/js/medien-welt.js?v=20260821p"></script> <script src="assets/js/medien-welt.js?v=20260821q"></script>
<script src="assets/js/i18n-medien-dogfather.js?v=20260821p"></script> <script src="assets/js/i18n-medien-dogfather.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </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');"> <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> <div id="site-header"></div>
@@ -65,11 +65,11 @@
</div> </div>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-media.js?v=20260821p"></script> <script src="assets/js/data-media.js?v=20260821q"></script>
<script src="assets/js/data-videos.js?v=20260821p"></script> <script src="assets/js/data-videos.js?v=20260821q"></script>
<script src="assets/js/data-aktionen.js?v=20260821p"></script> <script src="assets/js/data-aktionen.js?v=20260821q"></script>
<script src="assets/js/medien-welt.js?v=20260821p"></script> <script src="assets/js/medien-welt.js?v=20260821q"></script>
<script src="assets/js/i18n-medien-hasidog.js?v=20260821p"></script> <script src="assets/js/i18n-medien-hasidog.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+10 -10
View File
@@ -16,10 +16,10 @@
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="robots" content="noindex" /> <!-- generische URL, echte Team-Seiten verlinken hierher --> <meta name="robots" content="noindex" /> <!-- generische URL, echte Team-Seiten verlinken hierher -->
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
.profil-photo { aspect-ratio: 4/5; max-width: 420px; margin: 0 auto; } .profil-photo { aspect-ratio: 4/5; max-width: 420px; margin: 0 auto; }
.profil-photo img { width: 100%; height: 100%; object-fit: cover; display: block; } .profil-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
@@ -130,12 +130,12 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821p"></script> <script src="assets/js/data-modis.js?v=20260821q"></script>
<script src="assets/js/data-scouts.js?v=20260821p"></script> <script src="assets/js/data-scouts.js?v=20260821q"></script>
<script src="assets/js/data-creator.js?v=20260821p"></script> <script src="assets/js/data-creator.js?v=20260821q"></script>
<script src="assets/js/data-manager.js?v=20260821p"></script> <script src="assets/js/data-manager.js?v=20260821q"></script>
<script src="assets/js/i18n-profil.js?v=20260821p"></script> <script src="assets/js/i18n-profil.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", async () => { document.addEventListener("DOMContentLoaded", async () => {
const slug = new URLSearchParams(location.search).get("p") || ""; 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* Leere-Zustand-Kachel im "Gutschein"-Look: gestrichelter Rand wie ein /* Leere-Zustand-Kachel im "Gutschein"-Look: gestrichelter Rand wie ein
ausgeschnittener Coupon, damit auch OHNE echte Codes schon klar ist, ausgeschnittener Coupon, damit auch OHNE echte Codes schon klar ist,
@@ -131,8 +131,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-rabattcodes.js?v=20260821p"></script> <script src="assets/js/i18n-rabattcodes.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
(function () { (function () {
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
+9
View File
@@ -68,6 +68,7 @@ import { getEventsPublic, getEventsAdmin, saveEvent, uploadEventImage } from "./
import { getSpecialEventPublic, getSpecialEventAdmin, saveSpecialEvent } from "./routes/special-event.js"; import { getSpecialEventPublic, getSpecialEventAdmin, saveSpecialEvent } from "./routes/special-event.js";
import { import {
submitTestimonial, getApprovedTestimonialsPublic, getTestimonialsAdmin, setTestimonialStatus, deleteTestimonial, submitTestimonial, getApprovedTestimonialsPublic, getTestimonialsAdmin, setTestimonialStatus, deleteTestimonial,
uploadEigenesTestimonialBild,
setTestimonialAvatar, uploadTestimonialAvatar, setTestimonialAvatar, uploadTestimonialAvatar,
} from "./routes/testimonials.js"; } from "./routes/testimonials.js";
import { import {
@@ -195,6 +196,14 @@ app.post("/auth/login", wrap(login));
app.get("/events-of-year", wrap(getEventsPublic)); app.get("/events-of-year", wrap(getEventsPublic));
app.get("/special-event", wrap(getSpecialEventPublic)); app.get("/special-event", wrap(getSpecialEventPublic));
app.post("/testimonials/submit", wrap(submitTestimonial)); app.post("/testimonials/submit", wrap(submitTestimonial));
/* Öffentlich, ohne Login: eigenes Profilbild für die eigene Stimme
hochladen (21.08.2026). Bewusst mit derselben multer-Härtung wie der
Verwaltungs-Upload (nur JPG/PNG/WebP, max. 5 MB, zufälliger Dateiname);
zusätzlich bremst die Route selbst Massen-Uploads pro IP aus (siehe
uploadEigenesTestimonialBild in routes/testimonials.js). Sichtbar wird
ein hochgeladenes Bild erst, wenn die zugehörige Stimme freigegeben
wird -- Einreichungen landen immer zuerst in der Warteschlange. */
app.post("/testimonials/upload-avatar", stimmenBildUpload.single("image"), wrap(uploadEigenesTestimonialBild));
app.get("/testimonials", wrap(getApprovedTestimonialsPublic)); app.get("/testimonials", wrap(getApprovedTestimonialsPublic));
app.get("/team", wrap(getTeamPublic)); app.get("/team", wrap(getTeamPublic));
app.get("/site-texts", wrap(getSiteTextsPublic)); app.get("/site-texts", wrap(getSiteTextsPublic));
+60 -5
View File
@@ -36,10 +36,65 @@ function forbidden(res) {
Adresse oder etwas, das später als HTML/Adresse ausgewertet wird) und so Adresse oder etwas, das später als HTML/Adresse ausgewertet wird) und so
auf einer freigegebenen Stimme fremde Inhalte einbetten. Es werden auf einer freigegebenen Stimme fremde Inhalte einbetten. Es werden
deshalb ausschließlich diese festen Ids angenommen. */ deshalb ausschließlich diese festen Ids angenommen. */
const ERLAUBTE_AVATARE = [ /* 21.08.2026 auf die zwei Originalfiguren reduziert (Nutzer-Wunsch, siehe
"husky-hoodie", "hase-hoodie", "husky-kopf", "husky-kosmos", data-stimmen-avatare.js). Muss mit der Liste dort übereinstimmen.
"hase-kosmos", "casper", "casper-blick", "logo", Bereits freigegebene Stimmen mit einem der entfernten Bilder behalten
]; ihren Eintrag in der Datenbank -- sie zeigen dann wieder den
Anfangsbuchstaben, weil stimmeAvatarBild() die Id nicht mehr auflöst
(siehe dort: unbekannt -> null -> Buchstaben-Medaillon). Kein Fehler,
keine kaputten Bilder. */
const ERLAUBTE_AVATARE = ["husky-hoodie", "hase-hoodie"];
/* Eigenes hochgeladenes Bild (21.08.2026, Nutzer-Wunsch: "und die auswahl
wo die leute selbst ein bild rein setzen können").
Akzeptiert wird ausschließlich eine Adresse, die exakt auf unseren
eigenen Upload-Ordner zeigt und danach nur noch aus einem zufälligen
Dateinamen besteht (UUID + Endung, so legt multer sie an, siehe
index.js). Damit kann über dieses Feld weder eine fremde Adresse
eingeschleust noch per "../" aus dem Ordner ausgebrochen werden --
dieselbe Schutzabsicht wie bei der festen Id-Liste oben. */
const UPLOAD_BASIS = "https://postfach.dogfather-universe.com/uploads/testimonials/";
function istEigenesHochgeladenesBild(wert) {
if (!wert.startsWith(UPLOAD_BASIS)) return false;
const datei = wert.slice(UPLOAD_BASIS.length);
return /^[a-f0-9-]{36}\.(jpe?g|png|webp)$/i.test(datei);
}
/* Einfache Missbrauchsbremse für den öffentlichen Bild-Upload (kein Login
nötig). Ohne sie könnte jemand den Endpunkt in einer Schleife aufrufen
und die Festplatte des Servers vollschreiben. Bewusst schlicht im
Arbeitsspeicher gehalten: es gibt hier nur einen Serverprozess, und ein
Neustart (der den Zähler leert) ist kein Schaden. */
const UPLOAD_MAX_PRO_STUNDE = 10;
const uploadZaehler = new Map(); // ip -> { anzahl, fensterStart }
function uploadKontingentFrei(ip) {
const jetzt = Date.now();
const eintrag = uploadZaehler.get(ip);
if (!eintrag || jetzt - eintrag.fensterStart > 60 * 60 * 1000) {
uploadZaehler.set(ip, { anzahl: 1, fensterStart: jetzt });
return true;
}
if (eintrag.anzahl >= UPLOAD_MAX_PRO_STUNDE) return false;
eintrag.anzahl += 1;
return true;
}
/* Öffentlich (ohne Login): Profilbild für die eigene Einreichung hochladen.
Die Datei ist danach zwar unter ihrer zufälligen Adresse abrufbar, taucht
aber NIRGENDS auf, solange Dogi/VanVan die zugehörige Stimme nicht
freigeben -- jede Einreichung geht zuerst in die Warteschlange. Typ und
Größe begrenzt multer (nur JPG/PNG/WebP, max. 5 MB, zufälliger Name). */
export async function uploadEigenesTestimonialBild(req, res) {
const ip = req.ip || req.socket?.remoteAddress || "unbekannt";
if (!uploadKontingentFrei(ip)) {
return json(res, { ok: false, error: "Zu viele Uploads in kurzer Zeit. Bitte versuch es später nochmal." }, 429);
}
if (!req.file) return json(res, { ok: false, error: "Keine Bilddatei erhalten." }, 400);
const url = `${UPLOAD_BASIS}${req.file.filename}`;
logAction("anonym", "testimonial.eigenes_bild_hochgeladen", req.file.filename, null);
return json(res, { ok: true, url });
}
function oeffentlichesFeld(row) { function oeffentlichesFeld(row) {
return { return {
@@ -83,7 +138,7 @@ export async function submitTestimonial(req, res) {
// (statt die Einreichung abzulehnen) — die Nachricht ist das Wichtige, // (statt die Einreichung abzulehnen) — die Nachricht ist das Wichtige,
// und ein fehlendes Bild ist kein Grund, jemanden abzuweisen. // und ein fehlendes Bild ist kein Grund, jemanden abzuweisen.
const gewaehlt = String(body.avatar || "").trim(); const gewaehlt = String(body.avatar || "").trim();
const avatar = ERLAUBTE_AVATARE.includes(gewaehlt) ? gewaehlt : null; const avatar = ERLAUBTE_AVATARE.includes(gewaehlt) || istEigenesHochgeladenesBild(gewaehlt) ? gewaehlt : null;
const id = generateId(); const id = generateId();
db.prepare( db.prepare(
+7 -1
View File
@@ -194,6 +194,12 @@ export async function gateAuthHandler(req, res) {
let role = null; let role = null;
if (process.env.SITE_ACCESS_CODE_DOGI && timingSafeEqual(code, process.env.SITE_ACCESS_CODE_DOGI)) role = "dogi"; if (process.env.SITE_ACCESS_CODE_DOGI && timingSafeEqual(code, process.env.SITE_ACCESS_CODE_DOGI)) role = "dogi";
else if (process.env.SITE_ACCESS_CODE_VANVAN && timingSafeEqual(code, process.env.SITE_ACCESS_CODE_VANVAN)) role = "vanvan"; else if (process.env.SITE_ACCESS_CODE_VANVAN && timingSafeEqual(code, process.env.SITE_ACCESS_CODE_VANVAN)) role = "vanvan";
// Dritter Zugang, 21.08.2026 (Nutzer-Wunsch: "ich will nämlich dass Diene
// schon jetzt auch die Seite sieht, damit wir mit mehreren Augen die
// letzten kleinen Details verbessern"). Eigener Code + eigene Rolle statt
// Weitergabe eines bestehenden Codes -- so lässt sich der Zugang später
// einzeln wieder entziehen, ohne Dogi oder VanVan auszusperren.
else if (process.env.SITE_ACCESS_CODE_DIENE && timingSafeEqual(code, process.env.SITE_ACCESS_CODE_DIENE)) role = "diene";
if (!role) { if (!role) {
recordGateFailedAttempt(clientKey); recordGateFailedAttempt(clientKey);
@@ -211,7 +217,7 @@ export async function gateAuthHandler(req, res) {
export function gateMiddleware(req, res, next) { export function gateMiddleware(req, res, next) {
const pfad = req.path; const pfad = req.path;
const codesKonfiguriert = !!(process.env.SITE_ACCESS_SECRET && (process.env.SITE_ACCESS_CODE_DOGI || process.env.SITE_ACCESS_CODE_VANVAN)); const codesKonfiguriert = !!(process.env.SITE_ACCESS_SECRET && (process.env.SITE_ACCESS_CODE_DOGI || process.env.SITE_ACCESS_CODE_VANVAN || process.env.SITE_ACCESS_CODE_DIENE));
if (!codesKonfiguriert) return next(); if (!codesKonfiguriert) return next();
if (pfad === "/gate-auth" && req.method === "POST") { if (pfad === "/gate-auth" && req.method === "POST") {
+23 -7
View File
@@ -19,10 +19,10 @@
<meta name="twitter:description" content="Was die Community und das Team über DogFather sagen." /> <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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-stimmen.jpg');--page-bg-mobile:url('/assets/img/bg-stimmen-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -67,6 +67,21 @@
<label for="st-nachricht" data-i18n="st_form_nachricht_label">Deine Nachricht</label> <label for="st-nachricht" data-i18n="st_form_nachricht_label">Deine Nachricht</label>
<textarea id="st-nachricht" maxlength="500" rows="4" required data-i18n-attr="placeholder:st_form_nachricht_placeholder" placeholder="Erzähl uns, was dir Team Dogi bedeutet …"></textarea> <textarea id="st-nachricht" maxlength="500" rows="4" required data-i18n-attr="placeholder:st_form_nachricht_placeholder" placeholder="Erzähl uns, was dir Team Dogi bedeutet …"></textarea>
</div> </div>
<!-- Profilbild-Auswahl (21.08.2026, Nutzer-Wunsch: "ich soll auch
paar geile bilder zum aussuchen haben als profilbild falls die
leute kein bild mit schicken, das soll denen auch als
möglichkeit gegeben sein"). Bewusst OHNE Datei-Upload: das
Formular ist öffentlich und ohne Anmeldung erreichbar, ein
offener Upload wäre eine Einladung für beliebiges Material.
Wer ein eigenes Foto möchte, schickt es Dogi direkt — er kann
es in der Verwaltung hinterlegen. Die Kacheln werden aus
data-stimmen-avatare.js aufgebaut, damit Seite und Verwaltung
garantiert dieselbe Auswahl zeigen. -->
<div class="field">
<span class="stimmen-avatar-titel" data-i18n="st_form_avatar_label">Dein Profilbild (optional)</span>
<p class="small muted stimmen-avatar-hinweis" data-i18n="st_form_avatar_hinweis">Ohne Auswahl erscheint einfach der erste Buchstabe deines Namens.</p>
<div class="stimmen-avatar-wahl" id="stimmen-avatar-wahl" role="radiogroup" aria-labelledby="stimmen-avatar-titel"></div>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;" data-i18n="st_form_absenden">💙 Nachricht einreichen</button> <button type="submit" class="btn btn-primary" style="width:100%;" data-i18n="st_form_absenden">💙 Nachricht einreichen</button>
</form> </form>
<p class="stimmen-erfolg" id="stimmen-erfolg" data-i18n="st_form_erfolg">Danke dir! Deine Nachricht wurde eingereicht und wird von Dogi persönlich geprüft. 🩵</p> <p class="stimmen-erfolg" id="stimmen-erfolg" data-i18n="st_form_erfolg">Danke dir! Deine Nachricht wurde eingereicht und wird von Dogi persönlich geprüft. 🩵</p>
@@ -87,8 +102,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-stimmen.js?v=20260821p"></script> <script src="assets/js/data-stimmen-avatare.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/i18n-stimmen.js?v=20260821q"></script>
<script src="assets/js/stimmen.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script src="assets/js/stimmen.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamer.jpg');--page-bg-mobile:url('/assets/img/bg-streamer-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -217,7 +217,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-streamer.js?v=20260821p"></script> <script src="assets/js/i18n-streamer.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+9 -9
View File
@@ -19,17 +19,17 @@
<meta name="twitter:description" content="Wann geht DogFather live? Streamplan und Livebereich." /> <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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821p');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821p');"> <body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821q');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821q');">
<div id="site-header"></div> <div id="site-header"></div>
<main> <main>
<section class="hero page-bg"> <section class="hero page-bg">
<div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821p" alt="" loading="eager" /></div> <div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821q" alt="" loading="eager" /></div>
<div class="container"> <div class="container">
<span class="eyebrow" data-i18n="sp_eyebrow">Welt 1 — DogFather</span> <span class="eyebrow" data-i18n="sp_eyebrow">Welt 1 — DogFather</span>
<h1 data-i18n="sp_h1">Streamplan &amp; Livebereich</h1> <h1 data-i18n="sp_h1">Streamplan &amp; Livebereich</h1>
@@ -126,8 +126,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-streamplan.js?v=20260821p"></script> <script src="assets/js/i18n-streamplan.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script src="assets/js/streamplan.js?v=20260821p"></script> <script src="assets/js/streamplan.js?v=20260821q"></script>
</body> </body>
</html> </html>
+7 -7
View File
@@ -9,10 +9,10 @@
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0b0d10" /> <meta name="theme-color" content="#0b0d10" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* ===================================================================== /* =====================================================================
Supporter-Bereich — komplett neu gestaltet (Nutzer-Feedback Supporter-Bereich — komplett neu gestaltet (Nutzer-Feedback
@@ -515,9 +515,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-supporter.js?v=20260821p"></script> <script src="assets/js/i18n-supporter.js?v=20260821q"></script>
<script src="assets/js/supporter.js?v=20260821p"></script> <script src="assets/js/supporter.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
(function () { (function () {
const S = window.DogiSupporter; 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-modis.jpg');--page-bg-mobile:url('/assets/img/bg-modis.jpg');"> <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> <div id="site-header"></div>
@@ -72,9 +72,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-modis.js?v=20260821p"></script> <script src="assets/js/data-modis.js?v=20260821q"></script>
<script src="assets/js/i18n-team-modis.js?v=20260821p"></script> <script src="assets/js/i18n-team-modis.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const grid = document.getElementById("modis-grid"); 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:description" content="Die Scouts von Team Dogi bei Spicy Media." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-scouts.jpg');--page-bg-mobile:url('/assets/img/bg-scouts-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -49,9 +49,9 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/data-scouts.js?v=20260821p"></script> <script src="assets/js/data-scouts.js?v=20260821q"></script>
<script src="assets/js/i18n-team-scouts.js?v=20260821p"></script> <script src="assets/js/i18n-team-scouts.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const grid = document.getElementById("scouts-grid"); 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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* Gleiche Anpassung wie vanvan.html: der site-weit etwas größere /* Gleiche Anpassung wie vanvan.html: der site-weit etwas größere
Fließtext wirkt bei einem so langen persönlichen Brief zu wuchtig — Fließtext wirkt bei einem so langen persönlichen Brief zu wuchtig —
@@ -153,7 +153,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-tt-muddi.js?v=20260821p"></script> <script src="assets/js/i18n-tt-muddi.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* Auf Wunsch: der site-weit vergrößerte Fließtext wirkte auf VanVans /* Auf Wunsch: der site-weit vergrößerte Fließtext wirkte auf VanVans
eigener Seite zu groß (viel Text am Stück) — hier gezielt wieder eigener Seite zu groß (viel Text am Stück) — hier gezielt wieder
@@ -175,7 +175,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-vanvan.js?v=20260821p"></script> <script src="assets/js/i18n-vanvan.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </html>
+129 -10
View File
@@ -19,10 +19,10 @@
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-admin.png" /> <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="apple-mobile-web-app-title" content="DogiCrew-Verwaltung" />
<meta name="theme-color" content="#0e0a16" /> <meta name="theme-color" content="#0e0a16" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
<style> <style>
/* ===================================================================== /* =====================================================================
verwaltung.html — Dogis privater Command-Center-Bereich. verwaltung.html — Dogis privater Command-Center-Bereich.
@@ -69,6 +69,15 @@
Sättigung und weniger Helligkeit → sticht jetzt genauso klar hervor Sättigung und weniger Helligkeit → sticht jetzt genauso klar hervor
wie die anderen drei Akzentfarben. wie die anderen drei Akzentfarben.
===================================================================== */ ===================================================================== */
/* Profilbild-Auswahl in "Stimmen verwalten" (21.08.2026). Die Kacheln
selbst kommen aus main.css (.stimmen-avatar-*) — hier nur die Einbettung
in die Verwaltungs-Karte, etwas kleiner als auf der oeffentlichen Seite,
damit eine Einreichung nicht unnoetig hoch wird. */
.vw-stimme-avatar-block { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--border); }
.vw-stimme-avatar-wahl { grid-template-columns: repeat(auto-fill, minmax(46px, 1fr)); max-width: 520px; }
.vw-stimme-upload { display: block; margin-top: .8rem; color: var(--text-muted); }
.vw-stimme-upload input { display: block; margin-top: .3rem; }
#supp-root, #vw-stats-root, #session-bar { --accent: #7dd3fc; --accent-2: #38bdf8; --accent-contrast: #1a1408; } #supp-root, #vw-stats-root, #session-bar { --accent: #7dd3fc; --accent-2: #38bdf8; --accent-contrast: #1a1408; }
body { position: relative; } body { position: relative; }
/* Nutzer-Wunsch 04.08.2026 (3. Runde): "alles bisschen größer, Schrift /* Nutzer-Wunsch 04.08.2026 (3. Runde): "alles bisschen größer, Schrift
@@ -1207,12 +1216,13 @@
<!-- 21.08.2026: für den "Bestehende Mitglieder importieren"-Knopf in der <!-- 21.08.2026: für den "Bestehende Mitglieder importieren"-Knopf in der
Team-Verwaltung -- liest die von Hand gepflegten Profile aus diesen Team-Verwaltung -- liest die von Hand gepflegten Profile aus diesen
Dateien aus und bietet an, sie 1:1 in die Datenbank zu übernehmen. --> Dateien aus und bietet an, sie 1:1 in die Datenbank zu übernehmen. -->
<script src="assets/js/data-modis.js?v=20260821p"></script> <script src="assets/js/data-modis.js?v=20260821q"></script>
<script src="assets/js/data-scouts.js?v=20260821p"></script> <script src="assets/js/data-scouts.js?v=20260821q"></script>
<script src="assets/js/data-creator.js?v=20260821p"></script> <script src="assets/js/data-creator.js?v=20260821q"></script>
<script src="assets/js/data-manager.js?v=20260821p"></script> <script src="assets/js/data-manager.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/data-stimmen-avatare.js?v=20260821q"></script>
<script src="assets/js/admin-auth.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script src="assets/js/admin-auth.js?v=20260821q"></script>
<script> <script>
(function () { (function () {
const API_BASE_URL = "https://postfach.dogfather-universe.com"; const API_BASE_URL = "https://postfach.dogfather-universe.com";
@@ -1689,6 +1699,43 @@
stehen nur die sinnvollen Aktionen zur Verfügung. */ stehen nur die sinnvollen Aktionen zur Verfügung. */
const TESTIMONIAL_STATUS_LABELS = { eingereicht: "🟡 Neu", freigegeben: "🟢 Freigegeben", abgelehnt: "⚪ Abgelehnt" }; const TESTIMONIAL_STATUS_LABELS = { eingereicht: "🟡 Neu", freigegeben: "🟢 Freigegeben", abgelehnt: "⚪ Abgelehnt" };
/* Profilbild-Auswahl je Einreichung (21.08.2026, Nutzer-Wunsch: "ich soll
auch paar geile bilder zum aussuchen haben als profilbild"). Nutzt
dieselbe Liste wie die öffentliche Seite (data-stimmen-avatare.js), damit
Dogi genau die Bilder sieht, die es auch wirklich gibt. Zusätzlich zu den
Auswahlbildern kann hier ein eigenes Foto hochgeladen werden — das darf
bewusst NUR die Verwaltung, nicht das öffentliche Formular. */
function avatarWahlHtml(t) {
const liste = window.STIMMEN_AVATARE || [];
const aktiv = (wert) => (t.avatar === wert ? " aktiv" : "");
const eigenes = t.avatar && /^https?:\/\//i.test(t.avatar);
return `
<div class="vw-stimme-avatar-block">
<span class="small" style="color:var(--text-muted);display:block;margin-bottom:.45rem;">Profilbild</span>
<div class="stimmen-avatar-wahl vw-stimme-avatar-wahl">
<button type="button" class="stimmen-avatar-kachel${aktiv(null) || (t.avatar ? "" : " aktiv")}" data-avatar="" title="Kein Bild (Anfangsbuchstabe)">
<span class="stimmen-avatar-keins" aria-hidden="true">✦</span>
</button>
${liste
.map(
(a) =>
`<button type="button" class="stimmen-avatar-kachel${aktiv(a.id)}" data-avatar="${a.id}" title="${escapeHtml(a.name)}"><img src="${a.datei}" alt="" loading="lazy" /></button>`
)
.join("")}
${
eigenes
? `<button type="button" class="stimmen-avatar-kachel aktiv" data-avatar="${escapeHtml(t.avatar)}" title="Eigenes Foto"><img src="${escapeHtml(t.avatar)}" alt="" loading="lazy" /></button>`
: ""
}
</div>
<label class="small vw-stimme-upload">
Eigenes Foto hochladen (max. 5 MB, JPG/PNG/WebP)
<input type="file" class="testimonial-foto-input" accept="image/jpeg,image/png,image/webp" />
</label>
<p class="small testimonial-avatar-status" style="margin:.35rem 0 0;"></p>
</div>`;
}
function testimonialKarteHtml(t) { function testimonialKarteHtml(t) {
return `<div class="card vw-card vw-shine-card" style="margin-bottom:1rem;padding:1.3rem 1.4rem;" data-testimonial-id="${t.id}"> return `<div class="card vw-card vw-shine-card" style="margin-bottom:1rem;padding:1.3rem 1.4rem;" data-testimonial-id="${t.id}">
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;flex-wrap:wrap;"> <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;flex-wrap:wrap;">
@@ -1703,6 +1750,7 @@
</div> </div>
</div> </div>
<p style="margin:.8rem 0 0;">${escapeHtml(t.nachricht)}</p> <p style="margin:.8rem 0 0;">${escapeHtml(t.nachricht)}</p>
${avatarWahlHtml(t)}
</div>`; </div>`;
} }
@@ -1733,6 +1781,77 @@
await ladeUndRenderTestimonials(); await ladeUndRenderTestimonials();
}); });
}); });
/* Profilbild anklicken = sofort speichern. Bewusst OHNE die ganze Liste
neu zu laden: sonst springt die Ansicht bei jedem Klick nach oben und
man verliert die Stelle, an der man gerade arbeitet. Stattdessen wird
nur die Markierung innerhalb dieser einen Karte umgesetzt. */
listeEl.querySelectorAll(".vw-stimme-avatar-wahl").forEach((wahl) => {
wahl.addEventListener("click", async (e) => {
const kachel = e.target.closest(".stimmen-avatar-kachel");
if (!kachel) return;
const karte = kachel.closest("[data-testimonial-id]");
const statusEl = karte.querySelector(".testimonial-avatar-status");
const avatar = kachel.dataset.avatar || "";
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/testimonials/avatar", {
id: karte.dataset.testimonialId,
avatar,
});
if (!res.ok) {
statusEl.textContent = res.error || "Speichern fehlgeschlagen.";
statusEl.style.color = "#ff6b6b";
return;
}
wahl.querySelectorAll(".stimmen-avatar-kachel").forEach((b) => b.classList.remove("aktiv"));
kachel.classList.add("aktiv");
statusEl.textContent = "✅ Profilbild gespeichert.";
statusEl.style.color = "#baf7d6";
});
});
/* Eigenes Foto: wird sofort bei der Auswahl hochgeladen UND direkt als
Bild dieser Stimme gesetzt — ein zusätzlicher "Speichern"-Klick wäre
hier nur eine Stolperfalle (dieselbe Logik wie bei den Event-Bildern). */
listeEl.querySelectorAll(".testimonial-foto-input").forEach((input) => {
input.addEventListener("change", async () => {
const datei = input.files?.[0];
if (!datei) return;
const karte = input.closest("[data-testimonial-id]");
const statusEl = karte.querySelector(".testimonial-avatar-status");
statusEl.style.color = "";
statusEl.textContent = "Foto wird hochgeladen …";
const form = new FormData();
form.append("image", datei);
const session = window.AdminAuth.getSession();
try {
const res = await fetch(`${API_BASE_URL}/admin/testimonials/upload-avatar`, {
method: "POST",
headers: session?.token ? { Authorization: `Bearer ${session.token}` } : {},
body: form,
});
const data = await res.json();
if (!data.ok) {
statusEl.textContent = data.error || "Upload fehlgeschlagen.";
statusEl.style.color = "#ff6b6b";
return;
}
const gesetzt = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/testimonials/avatar", {
id: karte.dataset.testimonialId,
avatar: data.url,
});
if (!gesetzt.ok) {
statusEl.textContent = gesetzt.error || "Foto konnte nicht gesetzt werden.";
statusEl.style.color = "#ff6b6b";
return;
}
await ladeUndRenderTestimonials();
} catch {
statusEl.textContent = "Upload fehlgeschlagen (keine Verbindung).";
statusEl.style.color = "#ff6b6b";
}
});
});
} }
/* ===== "Team verwalten" — Modis/Scouts/Creator/Manager (21.08.2026) ===== /* ===== "Team verwalten" — Modis/Scouts/Creator/Manager (21.08.2026) =====
+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: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" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-werte.jpg');--page-bg-mobile:url('/assets/img/bg-werte-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -113,7 +113,7 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-werte.js?v=20260821p"></script> <script src="assets/js/i18n-werte.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
</body> </body>
</html> </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:description" content="Die Geschichte von DogFather in einer interaktiven Zeitleiste." />
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" /> <meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
<link rel="stylesheet" href="assets/css/main.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/main.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821q" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821p" /> <link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821q" />
</head> </head>
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-zeitreise.jpg');--page-bg-mobile:url('/assets/img/bg-zeitreise-mobile.jpg');"> <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> <div id="site-header"></div>
@@ -237,8 +237,8 @@
</main> </main>
<div id="site-footer"></div> <div id="site-footer"></div>
<script src="assets/js/i18n-zeitreise.js?v=20260821p"></script> <script src="assets/js/i18n-zeitreise.js?v=20260821q"></script>
<script src="assets/js/main.js?v=20260821p"></script> <script src="assets/js/main.js?v=20260821q"></script>
<script> <script>
/* Wachstums-Reveal: Äste blenden sich beim Scrollen sanft ein — passt /* Wachstums-Reveal: Äste blenden sich beim Scrollen sanft ein — passt
zum "wachsenden Baum"-Konzept dieser Seite. Rein page-lokal, betrifft zum "wachsenden Baum"-Konzept dieser Seite. Rein page-lokal, betrifft