DOGFATHER UNIVERSE: erste lauffähige Website + Bewerbungs-Postfach + Hosting-Vorbereitung
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/* =====================================================================
|
||||
data-aktionen.js — Aktionen & Projekte-Datenobjekt
|
||||
|
||||
aktionen.html rendert daraus die Übersichtskarten, aktion-detail.html
|
||||
liest per URL-Parameter (?id=slug) den passenden Eintrag aus und
|
||||
zeigt die Detailansicht — so gibt es EIN Template für alle Aktionen,
|
||||
ohne für jede neue Aktion eine neue HTML-Datei zu brauchen.
|
||||
|
||||
Felder:
|
||||
id – eindeutiger Slug (für aktion-detail.html?id=...)
|
||||
title, summary, description (Array von Absätzen)
|
||||
status – "geplant" | "läuft" | "abgeschlossen"
|
||||
tag – kurzer Kategorie-Text fürs Badge
|
||||
photo – Pfad zu assets/img/... oder "" für Platzhalter
|
||||
===================================================================== */
|
||||
|
||||
const AKTIONEN = [
|
||||
{
|
||||
id: "weihnachtsaktion-portugal",
|
||||
title: "Weihnachtsaktion & Portugal-Trip",
|
||||
tag: "Tierwohl",
|
||||
status: "geplant",
|
||||
summary: "Weihnachtsaktion für Tierheime in Portugal — Team Dogi hilft Tieren in Not.",
|
||||
description: [
|
||||
"Jedes Jahr organisiert Team Dogi eine Weihnachtsaktion zugunsten von Tierheimen in Portugal.",
|
||||
// TODO: genaue Details (Spendenziel, Ablauf, Partner-Tierheime, Termin) ergänzen.
|
||||
],
|
||||
photo: "",
|
||||
},
|
||||
{
|
||||
id: "community-treffen",
|
||||
title: "Community-Treffen (Geburtstag)",
|
||||
tag: "Community",
|
||||
status: "geplant",
|
||||
summary: "Ein persönliches Treffen mit der Community rund um Dogfathers Geburtstag.",
|
||||
description: [
|
||||
"Team Dogi ist Familie — deshalb gibt es ein reales Treffen mit der Community.",
|
||||
// TODO: Ort, Datum, Anmeldeprozess ergänzen.
|
||||
],
|
||||
photo: "",
|
||||
},
|
||||
{
|
||||
id: "vanvan-teddys",
|
||||
title: "Kooperation: VanVan Teddys",
|
||||
tag: "Merchandise",
|
||||
status: "läuft",
|
||||
summary: "Merchandise-Kooperation rund um VanVan — Teddys für die Community.",
|
||||
description: [
|
||||
"Gemeinsam mit VanVan entstehen limitierte Teddys als Teil des Merchandise-Angebots.",
|
||||
// TODO: Preis, Verfügbarkeit, Bestellweg ergänzen.
|
||||
],
|
||||
photo: "",
|
||||
},
|
||||
{
|
||||
id: "online-shop",
|
||||
title: "Online-Shop (in Planung)",
|
||||
tag: "Merchandise",
|
||||
status: "geplant",
|
||||
summary: "Der eigene Online-Shop für Tassen, Decken, Popsockets, Schlüsselanhänger, Sticker & mehr.",
|
||||
description: [
|
||||
"Aktuell läuft der Merchandise-Vertrieb nur über private Einzelbestellungen.",
|
||||
"Ziel: ein eigener Online-Shop, damit die Community jederzeit bestellen kann.",
|
||||
// TODO: Launch-Termin, Plattform (z.B. Shopify) ergänzen.
|
||||
],
|
||||
photo: "",
|
||||
},
|
||||
// TODO: weitere Aktionen/Projekte hier nach gleichem Muster ergänzen.
|
||||
];
|
||||
|
||||
if (typeof window !== "undefined") window.AKTIONEN = AKTIONEN;
|
||||
@@ -0,0 +1,27 @@
|
||||
/* =====================================================================
|
||||
data-creator.js — Creator-Datenobjekt (Creator im Spicy-Media-Team)
|
||||
|
||||
TODO: Noch keine Creator-Profile final geklärt (siehe Masterplan-
|
||||
Fragenkatalog, Block Creator/Team). Sobald Namen + Infos da sind,
|
||||
einfach nach dem Muster unten Objekte ins Array einfügen —
|
||||
creator.html liest automatisch aus und rendert Filter/Karten.
|
||||
|
||||
Felder:
|
||||
name, category (z.B. "Gaming", "Lifestyle", "Comedy" ...),
|
||||
role, photo, quote, social, status
|
||||
===================================================================== */
|
||||
|
||||
const CREATORS = [
|
||||
// Beispiel-Struktur (auskommentiert, zur Orientierung beim Befüllen):
|
||||
// {
|
||||
// name: "Name",
|
||||
// category: "Comedy",
|
||||
// role: "Creator bei Spicy Media",
|
||||
// photo: "assets/img/creator-name.jpg",
|
||||
// quote: "Kurzes Zitat",
|
||||
// social: { tiktok: "https://tiktok.com/@handle" },
|
||||
// status: "aktiv",
|
||||
// },
|
||||
];
|
||||
|
||||
if (typeof window !== "undefined") window.CREATORS = CREATORS;
|
||||
@@ -0,0 +1,94 @@
|
||||
/* =====================================================================
|
||||
data-modis.js — Modi-/Core-Team-Datenobjekt
|
||||
|
||||
Hierarchie (wichtig, bitte bei Änderungen respektieren):
|
||||
VanVan (rechte Hand, eigene Seite vanvan.html)
|
||||
> Diene (rechte Hand von VanVan)
|
||||
> Funny (Modi)
|
||||
> Miss (Modi + Cutterin)
|
||||
> Marina / Michi (Kommentare auf Clip-Accounts)
|
||||
|
||||
Feld "rank" steuert nur die Sortier-/Anzeigereihenfolge (1 = höchste
|
||||
Stufe). VanVan wird auf team-modis.html separat hervorgehoben UND hat
|
||||
eine eigene exklusive Seite (vanvan.html) — hier trotzdem gelistet,
|
||||
damit die Hierarchie an einer Stelle vollständig bleibt.
|
||||
|
||||
Felder: name, role, rank, photo, quote, social, page (optionale eigene
|
||||
Unterseite), status
|
||||
===================================================================== */
|
||||
|
||||
const MODIS = [
|
||||
{
|
||||
name: "VanVan",
|
||||
role: "Rechte Hand von Dogfather · Musik-Produzentin (Van-Van, Spotify)",
|
||||
rank: 1,
|
||||
photo: "",
|
||||
quote: "", // TODO: Zitat von VanVan einholen
|
||||
social: {},
|
||||
page: "vanvan.html",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Diene",
|
||||
role: "Organisiert & plant gemeinsam mit VanVan",
|
||||
rank: 2,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Funny",
|
||||
role: "Modi",
|
||||
rank: 3,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Miss",
|
||||
role: "Modi & Cutterin",
|
||||
rank: 4,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Marina",
|
||||
role: "Kommentare auf den Clip-Accounts",
|
||||
rank: 5,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Michi",
|
||||
role: "Kommentare auf den Clip-Accounts",
|
||||
rank: 5,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Dan",
|
||||
role: "Team Dogi — Rolle noch zu klären", // TODO: genaue Rolle von Dan im Team klären
|
||||
rank: 6,
|
||||
photo: "",
|
||||
quote: "",
|
||||
social: {},
|
||||
page: "",
|
||||
status: "aktiv",
|
||||
},
|
||||
// TODO: weitere Modis/Team-Mitglieder hier nach gleichem Muster ergänzen.
|
||||
];
|
||||
|
||||
if (typeof window !== "undefined") window.MODIS = MODIS;
|
||||
@@ -0,0 +1,42 @@
|
||||
/* =====================================================================
|
||||
data-scouts.js — Scout-Datenobjekt
|
||||
Scouts arbeiten mit Dogfather als Manager zusammen (Spicy Media),
|
||||
50/50 Gewinn-Sharing-Modell (Details siehe manager.html / TODO).
|
||||
|
||||
NEUEN SCOUT HINZUFÜGEN: einfach ein neues Objekt unten ins Array
|
||||
kopieren und Felder ausfüllen. Kein Code-Umbau nötig — team-scouts.html
|
||||
liest dieses Array automatisch aus.
|
||||
|
||||
Felder:
|
||||
name – Anzeigename (Groß-/Kleinschreibung korrekt!)
|
||||
role – Kurzrolle / Fokus
|
||||
photo – Pfad zu assets/img/... oder "" für Platzhalter-Avatar
|
||||
quote – kurzes Zitat/Motto (optional, "" wenn keins)
|
||||
social – { platform: "url", ... } optional
|
||||
status – "aktiv" | "neu" | "pausiert"
|
||||
===================================================================== */
|
||||
|
||||
const SCOUTS = [
|
||||
{
|
||||
name: "Patrick",
|
||||
role: "Scout — Team Dogi",
|
||||
photo: "",
|
||||
quote: "", // TODO: Zitat von Patrick einholen
|
||||
social: {},
|
||||
status: "aktiv",
|
||||
},
|
||||
{
|
||||
name: "Bananenstift",
|
||||
role: "Scout — Team Dogi",
|
||||
photo: "",
|
||||
quote: "", // TODO: Zitat von Bananenstift einholen
|
||||
social: {},
|
||||
status: "aktiv",
|
||||
},
|
||||
// TODO: Dogi liefert die vollständige, aktuelle Scout-Liste (Namen +
|
||||
// Kurzrolle) per Text nach — hier weitere Objekte nach obigem Muster
|
||||
// ergänzen. Struktur bleibt gleich, egal wie viele dazukommen.
|
||||
];
|
||||
|
||||
// Für <script>-Einbindung ohne Modul-Bundler global verfügbar machen
|
||||
if (typeof window !== "undefined") window.SCOUTS = SCOUTS;
|
||||
@@ -0,0 +1,129 @@
|
||||
/* =====================================================================
|
||||
forms.js — Validierung + Versand für die drei Bewerbungsformulare
|
||||
(Creator / Scout / Kooperation) auf bewerben.html
|
||||
|
||||
Versand-Logik:
|
||||
1) Wenn API_BASE_URL gesetzt ist (Cloudflare-Worker-Postfach deployed,
|
||||
siehe cloudflare-worker/README.md): Bewerbung wird per POST an
|
||||
`${API_BASE_URL}/submit` gesendet und landet im code-geschützten
|
||||
Postfach (postfach.html).
|
||||
2) Solange API_BASE_URL leer ist ODER der Versand fehlschlägt (z.B.
|
||||
offline, Worker noch nicht deployed): Fallback auf einen
|
||||
vorausgefüllten "mailto:"-Link, wie bisher.
|
||||
===================================================================== */
|
||||
|
||||
// TODO: nach dem Deploy des Cloudflare Workers hier die Worker-URL ohne
|
||||
// abschließenden Slash eintragen, z.B.
|
||||
// "https://dogfather-universe-postfach.DEIN-ACCOUNT.workers.dev"
|
||||
const API_BASE_URL = "";
|
||||
|
||||
const CONTACT_EMAIL = "[email protected]"; // TODO: echte Kontakt-E-Mail eintragen
|
||||
|
||||
const FORM_TYPES = {
|
||||
"form-creator": "creator",
|
||||
"form-scout": "scout",
|
||||
"form-kooperation": "kooperation",
|
||||
};
|
||||
|
||||
function validateField(field) {
|
||||
const wrapper = field.closest(".field");
|
||||
if (!wrapper) return true;
|
||||
let valid = field.checkValidity();
|
||||
|
||||
if (valid && field.type === "email" && field.value) {
|
||||
valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(field.value);
|
||||
}
|
||||
|
||||
wrapper.classList.toggle("has-error", !valid);
|
||||
return valid;
|
||||
}
|
||||
|
||||
function buildMailto(subjectLabel, data) {
|
||||
const lines = [...data.entries()].map(([k, v]) => `${k}: ${v}`).join("\n");
|
||||
return `mailto:${CONTACT_EMAIL}?subject=${encodeURIComponent(
|
||||
"Bewerbung: " + subjectLabel
|
||||
)}&body=${encodeURIComponent(lines)}`;
|
||||
}
|
||||
|
||||
async function submitToWorker(type, data) {
|
||||
if (!API_BASE_URL) return false;
|
||||
const payload = { type, data: Object.fromEntries(data.entries()) };
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/submit`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) return false;
|
||||
const json = await res.json();
|
||||
return !!json.ok;
|
||||
} catch {
|
||||
return false; // z.B. offline oder Worker nicht erreichbar -> Fallback greift
|
||||
}
|
||||
}
|
||||
|
||||
function setupForm(formId, subjectLabel) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
|
||||
const status = form.querySelector(".form-status");
|
||||
const type = FORM_TYPES[formId];
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const fields = [...form.querySelectorAll("input[required], select[required], textarea[required], input[type=email]")];
|
||||
const allValid = fields.map(validateField).every(Boolean);
|
||||
|
||||
if (!allValid) {
|
||||
if (status) {
|
||||
status.textContent = "Bitte prüfe die markierten Felder — es fehlt noch etwas.";
|
||||
status.className = "form-status show error";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = new FormData(form);
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
const delivered = await submitToWorker(type, data);
|
||||
|
||||
if (status) {
|
||||
if (delivered) {
|
||||
status.innerHTML = `Danke, das sieht super aus! 🩵 Deine Bewerbung ist sicher im Postfach von Team Dogi angekommen.`;
|
||||
status.className = "form-status show success";
|
||||
} else {
|
||||
const mailto = buildMailto(subjectLabel, data);
|
||||
status.innerHTML = `Danke, das sieht super aus! 🩵 Solange noch kein automatischer Versand eingerichtet ist:
|
||||
<a href="${mailto}" class="btn btn-outline" style="margin-top:.6rem;">E-Mail jetzt senden</a>`;
|
||||
status.className = "form-status show success";
|
||||
}
|
||||
}
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
form.reset();
|
||||
});
|
||||
|
||||
form.querySelectorAll("input, select, textarea").forEach((field) => {
|
||||
field.addEventListener("blur", () => validateField(field));
|
||||
});
|
||||
}
|
||||
|
||||
function setupTabs() {
|
||||
const tabs = document.querySelectorAll(".tab-btn");
|
||||
const panels = document.querySelectorAll(".tab-panel");
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("click", () => {
|
||||
tabs.forEach((t) => t.classList.remove("active"));
|
||||
panels.forEach((p) => p.classList.remove("active"));
|
||||
tab.classList.add("active");
|
||||
document.getElementById(tab.dataset.tab).classList.add("active");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
setupTabs();
|
||||
setupForm("form-creator", "Creator");
|
||||
setupForm("form-scout", "Scout");
|
||||
setupForm("form-kooperation", "Kooperation");
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
/* =====================================================================
|
||||
gallery.js — filterbare Mediengalerie + Lightbox (für medien.html)
|
||||
|
||||
MEDIA-Array unten mit echten Bildern befüllen sobald verfügbar
|
||||
(assets/img/...). Bis dahin rendert jeder Eintrag ohne "src" einen
|
||||
Platzhalter-Block mit passendem Alt-Text, damit die Struktur schon
|
||||
steht und nur noch Bilder eingehängt werden müssen.
|
||||
===================================================================== */
|
||||
|
||||
const MEDIA = [
|
||||
{ id: 1, category: "dogfather", alt: "Dogfather Profilbild", src: "" },
|
||||
{ id: 2, category: "dogfather", alt: "Dogfather beim Stream", src: "" },
|
||||
{ id: 3, category: "team", alt: "Team Dogi Gruppenbild", src: "" },
|
||||
{ id: 4, category: "team", alt: "Patrick (Scout)", src: "" },
|
||||
{ id: 5, category: "team", alt: "Bananenstift (Scout)", src: "" },
|
||||
{ id: 6, category: "team", alt: "VanVan", src: "" },
|
||||
{ id: 7, category: "hasidog", alt: "HasiDog Logo/Maskottchen", src: "" },
|
||||
{ id: 8, category: "hasidog", alt: "Husky Casper", src: "" },
|
||||
{ id: 9, category: "hasidog", alt: "Casper & Kater Simba", src: "" },
|
||||
{ id: 10, category: "manager", alt: "Spicy Media Logo", src: "" },
|
||||
{ id: 11, category: "aktionen", alt: "Weihnachtsaktion Portugal", src: "" },
|
||||
{ id: 12, category: "aktionen", alt: "VanVan Teddys Merch", src: "" },
|
||||
// TODO: echte Bilder aus den vom User gelieferten Assets ergänzen
|
||||
// (assets/img/... Pfad eintragen, dann wird automatisch das Bild
|
||||
// statt des Platzhalters angezeigt).
|
||||
];
|
||||
|
||||
function renderGalleryItem(item) {
|
||||
const media = item.src
|
||||
? `<img src="${item.src}" alt="${item.alt}" loading="lazy" />`
|
||||
: `<div class="img-placeholder">${item.alt}<br><span class="small">Foto folgt</span></div>`;
|
||||
return `
|
||||
<figure class="gallery-item card" data-category="${item.category}" data-index="${item.id}">
|
||||
${media}
|
||||
<figcaption class="small muted" style="margin-top:.6rem;">${item.alt}</figcaption>
|
||||
</figure>`;
|
||||
}
|
||||
|
||||
function initGallery() {
|
||||
const grid = document.getElementById("gallery-grid");
|
||||
const filterBar = document.getElementById("gallery-filters");
|
||||
if (!grid || !filterBar) return;
|
||||
|
||||
const categories = ["alle", ...new Set(MEDIA.map((m) => m.category))];
|
||||
filterBar.innerHTML = categories
|
||||
.map(
|
||||
(c, i) =>
|
||||
`<button class="filter-btn${i === 0 ? " active" : ""}" data-filter="${c}">${c[0].toUpperCase() + c.slice(1)}</button>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
function draw(filter) {
|
||||
const items = filter === "alle" ? MEDIA : MEDIA.filter((m) => m.category === filter);
|
||||
grid.innerHTML = items.map(renderGalleryItem).join("") || `<p class="muted">Keine Bilder in dieser Kategorie.</p>`;
|
||||
bindLightbox();
|
||||
}
|
||||
|
||||
filterBar.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".filter-btn");
|
||||
if (!btn) return;
|
||||
filterBar.querySelectorAll(".filter-btn").forEach((b) => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
draw(btn.dataset.filter);
|
||||
});
|
||||
|
||||
function bindLightbox() {
|
||||
const lb = document.getElementById("lightbox");
|
||||
const lbContent = document.getElementById("lightbox-content");
|
||||
if (!lb || !lbContent) return;
|
||||
grid.querySelectorAll(".gallery-item").forEach((el) => {
|
||||
el.addEventListener("click", () => {
|
||||
lbContent.innerHTML = el.innerHTML;
|
||||
lb.classList.add("open");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
draw("alle");
|
||||
|
||||
const lb = document.getElementById("lightbox");
|
||||
if (lb) {
|
||||
lb.addEventListener("click", (e) => {
|
||||
if (e.target === lb || e.target.closest(".lightbox-close")) {
|
||||
lb.classList.remove("open");
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") lb.classList.remove("open");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initGallery);
|
||||
@@ -0,0 +1,193 @@
|
||||
/* =====================================================================
|
||||
DOGFATHER UNIVERSE — main.js
|
||||
Gemeinsame Navigation, mobiles Menü, Footer, Nebel-Effekt.
|
||||
Läuft rein clientseitig (kein Build-Tool, kein Fetch von Partials),
|
||||
damit jede Seite auch direkt per Doppelklick (file://) funktioniert.
|
||||
===================================================================== */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/* ---------- Navigationsstruktur (an einer Stelle pflegen) ---------- */
|
||||
const NAV = [
|
||||
{ label: "Start", href: "index.html" },
|
||||
{
|
||||
label: "Dogfather",
|
||||
children: [
|
||||
{ label: "Die Person", href: "dogfather.html" },
|
||||
{ label: "Streamer", href: "streamer.html" },
|
||||
{ label: "Streamplan", href: "streamplan.html" },
|
||||
{ label: "Zeitreise", href: "zeitreise.html" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "HasiDog",
|
||||
children: [
|
||||
{ label: "HasiDog-Welt", href: "hasidog.html" },
|
||||
{ label: "Kasper", href: "kasper.html" },
|
||||
{ label: "Community", href: "community.html" },
|
||||
{ label: "Stimmen", href: "stimmen.html" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Manager",
|
||||
children: [
|
||||
{ label: "Manager Dogfather", href: "manager.html" },
|
||||
{ label: "Werte", href: "werte.html" },
|
||||
{ label: "Scout-Team", href: "team-scouts.html" },
|
||||
{ label: "Modi-Team", href: "team-modis.html" },
|
||||
{ label: "VanVan", href: "vanvan.html" },
|
||||
{ label: "Creator", href: "creator.html" },
|
||||
{ label: "Bewerben", href: "bewerben.html" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Medien",
|
||||
children: [
|
||||
{ label: "Galerie", href: "medien.html" },
|
||||
{ label: "Videos", href: "videos.html" },
|
||||
{ label: "Aktionen", href: "aktionen.html" },
|
||||
],
|
||||
},
|
||||
{ label: "Links", href: "links.html" },
|
||||
];
|
||||
|
||||
const currentFile = (location.pathname.split("/").pop() || "index.html");
|
||||
|
||||
function isActive(href) {
|
||||
return href === currentFile;
|
||||
}
|
||||
|
||||
function renderNavItem(item) {
|
||||
if (!item.children) {
|
||||
return `<a class="nav-flat${isActive(item.href) ? " active" : ""}" href="${item.href}">${item.label}</a>`;
|
||||
}
|
||||
const activeChild = item.children.some((c) => isActive(c.href));
|
||||
return `
|
||||
<div class="nav-group${activeChild ? " open-hint" : ""}">
|
||||
<button type="button" aria-expanded="false">${item.label} <span aria-hidden="true">▾</span></button>
|
||||
<div class="nav-dropdown">
|
||||
${item.children
|
||||
.map(
|
||||
(c) =>
|
||||
`<a href="${c.href}"${isActive(c.href) ? ' class="active"' : ""}>${c.label}</a>`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderHeader() {
|
||||
const el = document.getElementById("site-header");
|
||||
if (!el) return;
|
||||
el.innerHTML = `
|
||||
<header class="site-header">
|
||||
<div class="nav-bar">
|
||||
<a class="brand" href="index.html">
|
||||
<span class="heart">🩵</span>
|
||||
<span>DOGFATHER UNIVERSE
|
||||
<small>Streamer · Community · HasiDog · Manager</small>
|
||||
</span>
|
||||
</a>
|
||||
<button class="nav-toggle" type="button" aria-label="Menü öffnen" aria-expanded="false">☰</button>
|
||||
<nav class="nav-links">
|
||||
${NAV.map(renderNavItem).join("")}
|
||||
<a class="nav-cta" href="bewerben.html">Bewerben 🩵</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>`;
|
||||
|
||||
const toggle = el.querySelector(".nav-toggle");
|
||||
const links = el.querySelector(".nav-links");
|
||||
toggle.addEventListener("click", () => {
|
||||
const open = links.classList.toggle("open");
|
||||
toggle.setAttribute("aria-expanded", String(open));
|
||||
});
|
||||
|
||||
el.querySelectorAll(".nav-group > button").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const group = btn.parentElement;
|
||||
const wasOpen = group.classList.contains("open");
|
||||
el.querySelectorAll(".nav-group.open").forEach((g) => g.classList.remove("open"));
|
||||
if (!wasOpen) group.classList.add("open");
|
||||
btn.setAttribute("aria-expanded", String(!wasOpen));
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!el.contains(e.target)) {
|
||||
el.querySelectorAll(".nav-group.open").forEach((g) => g.classList.remove("open"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderFooter() {
|
||||
const el = document.getElementById("site-footer");
|
||||
if (!el) return;
|
||||
const year = new Date().getFullYear();
|
||||
el.innerHTML = `
|
||||
<footer class="site-footer">
|
||||
<div class="container footer-grid">
|
||||
<div>
|
||||
<div class="brand"><span class="heart">🩵</span> DOGFATHER UNIVERSE</div>
|
||||
<p class="small" style="margin-top:.8rem;max-width:32ch;">
|
||||
Streamer. Community. HasiDog. Manager. Gemeinsam stark.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Dogfather</h4>
|
||||
<ul>
|
||||
<li><a href="dogfather.html">Die Person</a></li>
|
||||
<li><a href="streamer.html">Streamer</a></li>
|
||||
<li><a href="zeitreise.html">Zeitreise</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>HasiDog & Manager</h4>
|
||||
<ul>
|
||||
<li><a href="hasidog.html">HasiDog-Welt</a></li>
|
||||
<li><a href="manager.html">Manager Dogfather</a></li>
|
||||
<li><a href="team-scouts.html">Scouts</a></li>
|
||||
<li><a href="team-modis.html">Modis</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Mehr</h4>
|
||||
<ul>
|
||||
<li><a href="links.html">Alle Links</a></li>
|
||||
<li><a href="bewerben.html">Bewerben</a></li>
|
||||
<li><a href="kontakt.html">Kontakt</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container footer-bottom">
|
||||
<span>© ${year} Dogfather Universe. Team Dogi ist Familie 🩵</span>
|
||||
<span><a href="kontakt.html">Impressum & Datenschutz</a> · <a href="postfach.html">Team-Postfach 🔒</a></span>
|
||||
</div>
|
||||
</footer>`;
|
||||
}
|
||||
|
||||
function renderFog() {
|
||||
if (document.querySelector(".fog-layer")) return;
|
||||
const layer = document.createElement("div");
|
||||
layer.className = "fog-layer";
|
||||
layer.setAttribute("aria-hidden", "true");
|
||||
const blobs = 6;
|
||||
let html = "";
|
||||
for (let i = 0; i < blobs; i++) {
|
||||
const size = 220 + Math.round(Math.random() * 260);
|
||||
const top = Math.round(Math.random() * 100);
|
||||
const left = Math.round(Math.random() * 100);
|
||||
const delay = (Math.random() * 10).toFixed(1);
|
||||
html += `<span style="width:${size}px;height:${size}px;top:${top}%;left:${left}%;animation-delay:${delay}s;"></span>`;
|
||||
}
|
||||
layer.innerHTML = html;
|
||||
document.body.prepend(layer);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
renderHeader();
|
||||
renderFooter();
|
||||
renderFog();
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user