Alle ausstehenden Aenderungen (Bilder, Texte) fuer den Server-Umzug uebernommen

This commit is contained in:
2026-08-05 18:05:53 +02:00
parent 9af0698056
commit e331965bf1
87 changed files with 8296 additions and 309 deletions
+205 -1
View File
@@ -82,6 +82,7 @@
nav_dogfather_casper: { de: "Casper", "de-CH": "Casper", en: "Casper", fr: "Casper", pt: "Casper" },
nav_dogfather_vanvan: { de: "VanVan", "de-CH": "VanVan", en: "VanVan", fr: "VanVan", pt: "VanVan" },
nav_dogfather_zeitreise: { de: "Zeitreise", "de-CH": "Zytreis", en: "Timeline", fr: "Chronologie", pt: "Linha do tempo" },
nav_dogfather_ttmuddi: { de: "TT-Muddi", "de-CH": "TT-Muddi", en: "TT-Muddi", fr: "TT-Muddi", pt: "TT-Muddi" },
nav_hasidog: { de: "HasiDog", "de-CH": "HasiDog", en: "HasiDog", fr: "HasiDog", pt: "HasiDog" },
nav_hasidog_welt: { de: "HasiDog-Welt", "de-CH": "HasiDog-Wält", en: "HasiDog World", fr: "Monde HasiDog", pt: "Mundo HasiDog" },
nav_hasidog_modi: { de: "Modi-Team", "de-CH": "Modi-Team", en: "Modi-Team", fr: "Modi-Team", pt: "Modi-Team" },
@@ -100,6 +101,9 @@
nav_medien_casper: { de: "Casper", "de-CH": "Casper", en: "Casper", fr: "Casper", pt: "Casper" },
nav_links: { de: "Links", "de-CH": "Links", en: "Links", fr: "Liens", pt: "Links" },
nav_cta_bewerben: { de: "Bewerben 🩵", "de-CH": "Bewärbe 🩵", en: "Apply 🩵", fr: "Postuler 🩵", pt: "Candidatar-se 🩵" },
nav_cta_abo_anonym: { de: "DogiCrew 👑", "de-CH": "DogiCrew 👑", en: "DogiCrew 👑", fr: "DogiCrew 👑", pt: "DogiCrew 👑" },
nav_cta_abo_aktivieren: { de: "Abo aktivieren 👑", "de-CH": "Abo aktiviere 👑", en: "Activate Subscription 👑", fr: "Activer l'abonnement 👑", pt: "Ativar assinatura 👑" },
nav_cta_abo_bereich: { de: "Supporter-Bereich 👑", "de-CH": "Supporter-Bereich 👑", en: "Supporter Area 👑", fr: "Espace Supporter 👑", pt: "Área do Apoiador 👑" },
nav_toggle_aria: { de: "Menü öffnen", "de-CH": "Menü uftue", en: "Open menu", fr: "Ouvrir le menu", pt: "Abrir menu" },
sprache_aria: { de: "Sprache wählen", "de-CH": "Sprach uswähle", en: "Choose language", fr: "Choisir la langue", pt: "Escolher idioma" },
brand_tagline: {
@@ -192,6 +196,7 @@
{ key: "nav_dogfather_streamer", href: "streamer.html" },
{ key: "nav_dogfather_casper", href: "casper.html" },
{ key: "nav_dogfather_vanvan", href: "vanvan.html" },
{ key: "nav_dogfather_ttmuddi", href: "tt-muddi.html" },
{ key: "nav_dogfather_zeitreise", href: "zeitreise.html" },
],
},
@@ -313,6 +318,173 @@
</div>`;
}
/* ===================================================================
Vorschau-Modus für Dogi + VanVan (Zugangsschranke, siehe gate-worker.js)
Solange die Seite noch nicht öffentlich ist, kommen nur Dogi und VanVan
per Zugangscode rein (gate-worker.js setzt dabei ein lesbares Cookie
"dogi_role=dogi"/"vanvan" — NICHT das eigentliche Sicherheits-Cookie,
das bleibt HttpOnly). Damit die beiden die Seite auch "als Abonnent"
bzw. "als Nicht-Abonnent" ansehen können, OHNE ein echtes Abo
abschließen zu müssen, gibt es einen kleinen Umschalter unten links,
der NUR erscheint, wenn das Rollen-Cookie gesetzt ist. Die Wahl landet
in localStorage und wird von renderAboButton() sowie von supporter.js/
supporter.html (Mock-Daten statt echtem API-Aufruf) ausgelesen.
=================================================================== */
const VORSCHAU_SCHLUESSEL = "dogiPreviewMode"; // "abonnent" | "nicht-abonnent" | leer = echter Stand
function getCookieWert(name) {
const treffer = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
return treffer ? decodeURIComponent(treffer[1]) : null;
}
function getVorschauRolle() {
return getCookieWert("dogi_role"); // "dogi" | "vanvan" | null
}
/* Realistischer Anzeigename fürs Vorschau-Konto — Nutzer-Feedback
03.08.2026: "ich will in der Vorschau alles sehen wie es fertig sein
wird, nix da stehen haben von Vorschau, das bringt mir nix." Statt dem
Wort "Vorschau" irgendwo im Text zeigt die Vorschau jetzt einfach den
Namen der Person, die gerade eingeloggt ist (Dogi oder VanVan) — sieht
dadurch exakt so aus wie ein echtes, fertiges Konto. */
function vorschauAnzeigename() {
const rolle = getVorschauRolle();
return rolle === "vanvan" ? "VanVan" : "Dogi";
}
window.dogiVorschauName = vorschauAnzeigename; // öffentlich, für supporter.html
function getVorschauModus() {
if (!getVorschauRolle()) return null; // Umschalter nur für Dogi/VanVan gültig
const wert = localStorage.getItem(VORSCHAU_SCHLUESSEL);
return wert === "abonnent" || wert === "nicht-abonnent" ? wert : null;
}
function setVorschauModus(modus) {
if (modus) localStorage.setItem(VORSCHAU_SCHLUESSEL, modus);
else localStorage.removeItem(VORSCHAU_SCHLUESSEL);
renderHeader();
renderVorschauSchalter();
document.dispatchEvent(new CustomEvent("dogi-vorschau-geaendert", { detail: { modus } }));
}
window.dogiVorschauModus = getVorschauModus; // öffentlich, für supporter.html/abonnieren.html
/* Aufklappbarer Kreisknopf unten links, spiegelbildlich zum Musik-Knopf
unten rechts (gleiche Bauart, siehe .vorschau-toggle/.vorschau-panel in
main.css) — Nutzer-Feedback 03.08.2026: statt einer dauerhaft offenen
Text-Knopfleiste "so ein Button wie bei VanVan", der sich erst bei
Klick öffnet. Nur EINMAL erzeugt (wie beim Musik-Widget), damit ein
offenes Panel beim nahtlosen Seitenwechsel nicht jedes Mal neu gebaut
wird — nur der Inhalt (aktive Auswahl) wird bei jedem Aufruf aktuell
gehalten. */
function renderVorschauSchalter() {
if (!getVorschauRolle()) return; // öffentliche Besucher sehen nie etwas
let widget = document.getElementById("vorschau-widget");
if (!widget) {
widget = document.createElement("div");
widget.className = "vorschau-widget";
widget.id = "vorschau-widget";
widget.innerHTML = `
<div class="vorschau-panel" id="vorschau-panel" hidden>
<div class="vorschau-panel-head">
<strong>🎭 Vorschau</strong>
<button class="vorschau-close" id="vorschau-close" type="button" aria-label="Vorschau-Feld schließen">✕</button>
</div>
<div class="vorschau-optionen" id="vorschau-optionen"></div>
</div>
<button class="vorschau-toggle" id="vorschau-toggle" type="button"
aria-expanded="false" aria-controls="vorschau-panel"
title="Vorschau: als Abonnent/Nicht-Abonnent ansehen"><span>🎭</span></button>`;
document.body.appendChild(widget);
const panel = document.getElementById("vorschau-panel");
const toggle = document.getElementById("vorschau-toggle");
const oeffnen = (auf) => {
panel.hidden = !auf;
toggle.setAttribute("aria-expanded", String(auf));
widget.classList.toggle("open", auf);
};
toggle.addEventListener("click", () => oeffnen(panel.hidden));
document.getElementById("vorschau-close").addEventListener("click", () => oeffnen(false));
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !panel.hidden) oeffnen(false);
});
document.getElementById("vorschau-optionen").addEventListener("click", (e) => {
const btn = e.target.closest("button[data-vorschau]");
if (!btn) return;
setVorschauModus(btn.dataset.vorschau === "echt" ? null : btn.dataset.vorschau);
});
}
const aktuell = getVorschauModus();
const knopf = (wert, label) => {
const aktiv = wert === "echt" ? !aktuell : aktuell === wert;
return `<button type="button" data-vorschau="${wert}" class="${aktiv ? "aktiv" : ""}">${aktiv ? "✓ " : ""}${label}</button>`;
};
document.getElementById("vorschau-optionen").innerHTML =
knopf("echt", "Echter Login-Stand") + knopf("abonnent", "Als Abonnent") + knopf("nicht-abonnent", "Als Nicht-Abonnent");
}
/* Abo-Button in der Hauptnavigation (Dogfather_VanVan_Supporter_Abo.odt,
Abschnitt 3) — Beschriftung/Ziel hängt vom Supporter-Login-Stand ab.
Bewusst KEIN API-Aufruf bei jedem Seitenaufruf (würde jede einzelne
Seite der Website an den Worker koppeln) — stattdessen wird der zuletzt
bekannte Stand aus localStorage gelesen, den supporter.html/
abonnieren.html bei jedem eigenen Laden aktuell hält (siehe dort).
Vorschau-Modus (Dogi/VanVan, siehe oben) überschreibt das für den
Nav-Knopf, unabhängig vom echten Login-Stand.
Nutzer-Feedback 03.08.2026: Ist man eingeloggt UND hat ein Abo, soll im
Knopf nicht der generische Text "Supporter-Bereich" stehen, sondern der
eigene Benutzername (aus /supporter/me → profile.displayName, wird von
abonnieren.html/supporter.html per S.cacheDisplayName() aktuell
gehalten). displayName ist freier Nutzertext -> escapeHtml() ist
Pflicht (sonst XSS über den eigenen Anzeigenamen möglich), und wird auf
eine im Knopf sinnvolle Länge gekürzt. */
function escapeHtml(text) {
return String(text).replace(/[&<>"']/g, (z) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[z]));
}
function kuerzen(text, maxLaenge) {
return text.length > maxLaenge ? text.slice(0, maxLaenge - 1).trimEnd() + "…" : text;
}
function bereichLabel(name) {
const sauber = (name || "").trim();
if (!sauber) return t("nav_cta_abo_bereich");
return `${escapeHtml(kuerzen(sauber, 18))} 👑`;
}
function renderAboButton() {
const vorschau = getVorschauModus();
if (vorschau === "abonnent") {
// Ist Dogi/VanVan zufällig wirklich mit einem echten Konto
// eingeloggt (echter Name im Speicher), soll auch in der Vorschau
// DER echte Name stehen. Ohne echtes Konto fällt es auf den Namen
// der eingeloggten Rolle zurück (vorschauAnzeigename() — "Dogi" oder
// "VanVan"), NICHT auf einen erkennbaren "Vorschau"-Platzhalter,
// damit die Vorschau exakt so aussieht wie das fertige Ergebnis
// (Nutzer-Feedback 03.08.2026: "nix da stehen haben von Vorschau,
// das bringt mir nix").
const echterName = localStorage.getItem("supporterDisplayName");
return `<a class="nav-cta nav-cta-abo" href="supporter.html"><span>${bereichLabel(echterName || vorschauAnzeigename())}</span></a>`;
}
if (vorschau === "nicht-abonnent") {
return `<a class="nav-cta nav-cta-abo" href="abonnieren.html"><span>${t("nav_cta_abo_anonym")}</span></a>`;
}
const token = localStorage.getItem("supporterToken");
const status = localStorage.getItem("supporterSubStatus");
if (!token) {
return `<a class="nav-cta nav-cta-abo" href="abonnieren.html"><span>${t("nav_cta_abo_anonym")}</span></a>`;
}
const hatBereich = ["active", "pending", "suspended", "cancelled"].includes(status);
if (hatBereich) {
const name = localStorage.getItem("supporterDisplayName");
return `<a class="nav-cta nav-cta-abo" href="supporter.html"><span>${bereichLabel(name)}</span></a>`;
}
return `<a class="nav-cta nav-cta-abo" href="abonnieren.html"><span>${t("nav_cta_abo_aktivieren")}</span></a>`;
}
function renderHeader() {
const el = document.getElementById("site-header");
if (!el) return;
@@ -329,6 +501,7 @@
<nav class="nav-links">
${NAV.map(renderNavItem).join("")}
${renderSprachSchalter()}
${renderAboButton()}
<a class="nav-cta" href="bewerben.html"><span>${t("nav_cta_bewerben")}</span></a>
</nav>
</div>
@@ -389,6 +562,7 @@
<li><a href="streamer.html">${t("nav_dogfather_streamer")}</a></li>
<li><a href="casper.html">${t("nav_dogfather_casper")}</a></li>
<li><a href="vanvan.html">${t("nav_dogfather_vanvan")}</a></li>
<li><a href="tt-muddi.html">${t("nav_dogfather_ttmuddi")}</a></li>
<li><a href="zeitreise.html">${t("nav_dogfather_zeitreise")}</a></li>
</ul>
</div>
@@ -415,7 +589,7 @@
</div>
<div class="container footer-bottom">
<span>${t("footer_copyright").replace("{jahr}", year)}</span>
<span><a href="kontakt.html">${t("footer_impressum")}</a></span>
<span><a href="kontakt.html">${t("footer_impressum")}</a> · <a href="agb.html">AGB</a></span>
</div>
</footer>`;
}
@@ -750,6 +924,28 @@
}
alterInhalt.replaceWith(neuerInhalt);
// Seiteneigene <style>-Blöcke im <head> mitnehmen (Nutzer-Feedback
// 03.08.2026: "wenn ich den Abo-Knopf drücke, sieht die Seite zuerst
// kaputt aus, erst nach einem manuellen Neuladen stimmt es"). Ursache:
// viele Seiten (abonnieren.html, supporter.html, rabattcodes.html,
// tt-muddi.html, ...) haben ihr eigenes Feintuning als <style> direkt
// im <head> stehen. Beim nahtlosen Wechsel wurde bisher NUR <main>
// ausgetauscht — der <head> blieb unverändert, das eigene <style> der
// neuen Seite kam nie mit. Ein echter Neuladen (F5) parst das
// Dokument komplett neu und hatte das Problem deshalb nie gezeigt.
// Alle mit main.js/main.css geteilten Styles kommen ausschließlich
// über <link rel="stylesheet">, nie über ein <style> im <head> — jedes
// <style>-Element dort ist also garantiert seitenEIGEN und darf beim
// Wechsel 1:1 ersetzt werden.
document.head.querySelectorAll("style[data-seiteneigen]").forEach((el) => el.remove());
dok.head.querySelectorAll("style").forEach((el) => {
const neu = document.createElement("style");
neu.setAttribute("data-seiteneigen", "");
neu.textContent = el.textContent;
document.head.appendChild(neu);
});
// Seiteneigenes i18n-Wörterbuch zurücksetzen — sonst könnte das der
// VORHERIGEN Seite (z.B. Startseite) fälschlich auf der neuen Seite
// weiterwirken. Hat die neue Seite ein eigenes (z.B. i18n-index.js),
@@ -832,10 +1028,18 @@
}
document.addEventListener("DOMContentLoaded", () => {
// Das <style>, das die ERSTE Seite selbst mitbringt (roh aus dem HTML
// geparst, noch ohne unsere Markierung), muss dieselbe Markierung wie
// die später per SPA-Wechsel nachgeladenen bekommen — sonst würde
// seiteLaden() es nie wieder entfernen können und es bliebe für immer
// im <head> hängen, sobald man einmal weiterklickt (siehe Kommentar
// dort für den vollen Zusammenhang).
document.head.querySelectorAll("style:not([data-seiteneigen])").forEach((el) => el.setAttribute("data-seiteneigen", ""));
renderHeader();
renderFooter();
renderFog();
renderMusicWidget();
renderVorschauSchalter();
musikKnopfFarbenAnpassen();
nahtloseNavigationAktivieren();
applyTranslations(); // seiteneigene data-i18n-Elemente (z.B. Startseite) übersetzen