Alle ausstehenden Aenderungen (Bilder, Texte) fuer den Server-Umzug uebernommen
This commit is contained in:
+205
-1
@@ -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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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
|
||||
|
||||
Reference in New Issue
Block a user