Klickbare Profilseiten fuer jedes Team-Mitglied

Wunsch: Klick auf eine Person (z.B. Bananenstift) oeffnet eine eigene
Seite mit grossem Foto und Vorstellungstext daneben - fuer jeden.

- Neue Seite profil.html: generische Profilseite, liest Person per
  ?p=<slug> aus MODIS/SCOUTS/CREATORS, zeigt grosses Foto (4:5, farbiger
  Rahmen passend zur Person), Name, Rolle, Vorstellungstext, optional
  Zitat und Social-Links. Welt/Theme (blau=Team Dogi, rot=Agentur) und
  Hintergrundmotiv passen sich automatisch an, ob die Person Modi,
  Scout oder Creator ist. Unbekannter Slug zeigt eine freundliche
  Fehlermeldung statt kaputter Seite.
- data-modis.js, data-scouts.js, data-creator.js: neues Feld "slug"
  (eindeutige ID fuer die URL) und "bio" (Vorstellungstext) fuer jede
  Person ergaenzt. VanVan behaelt ihre eigene, ausfuehrlichere Seite
  (vanvan.html) und wird nicht auf profil.html umgeleitet.
- team-modis.html, team-scouts.html, creator.html, index.html
  (Team-Vorschau): Karten sind jetzt komplett anklickbar (ganze Karte
  ist ein Link), nicht mehr nur ein kleiner Knopf.
- Bugfix: gerade curly quotes ("„...\"") mitten in einem doppelt
  angefuehrten JS-String schlossen den String vorzeitig ab und haetten
  main.js kaputt gemacht (per node --check gefunden, bevor es live ging).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-07-31 20:21:16 +02:00
co-authored by Claude Opus 5
parent b728d3f89d
commit 87c95dd0f8
8 changed files with 220 additions and 13 deletions
+6 -2
View File
@@ -7,17 +7,21 @@
creator.html liest automatisch aus und rendert Filter/Karten.
Felder:
name, category (z.B. "Gaming", "Lifestyle", "Comedy" ...),
role, photo, quote, social, status
name, slug (eindeutig, für profil.html?p=slug),
category (z.B. "Gaming", "Lifestyle", "Comedy" ...),
role, photo, bio (Vorstellungstext für die Profilseite),
quote, social, status
===================================================================== */
const CREATORS = [
// Beispiel-Struktur (auskommentiert, zur Orientierung beim Befüllen):
// {
// name: "Name",
// slug: "name",
// category: "Comedy",
// role: "Creator bei Spicy Media",
// photo: "assets/img/creator-name.jpg",
// bio: "Kurzer Vorstellungstext für die eigene Profilseite.",
// quote: "Kurzes Zitat",
// social: { tiktok: "https://tiktok.com/@handle" },
// status: "aktiv",
+18 -2
View File
@@ -13,17 +13,21 @@
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
Felder: name, slug (eindeutig, für profil.html?p=slug), role, rank,
photo, avatar, bio (Vorstellungstext für die Profilseite), quote,
social, page (optionale EIGENE Unterseite statt der generischen
Profilseite — aktuell nur VanVan), status
===================================================================== */
const MODIS = [
{
name: "VanVan",
slug: "vanvan",
role: "Rechte Hand von Dogfather · Musik-Produzentin (Van-Van, Spotify)",
rank: 1,
photo: "assets/img/card-vanvan.jpg",
avatar: "assets/img/avatar-vanvan.jpg",
bio: "VanVan ist die rechte Hand von Dogfather — und seine beste Freundin. Sie bekommt täglich die Neuigkeiten, Aufgaben und Infos direkt von ihm und verteilt bzw. delegiert sie im Team, oder organisiert gemeinsam mit Diene. Neben ihrer Rolle im Team ist sie außerdem Musik-Produzentin unter dem Namen „Van-Van”.",
quote: "", // TODO: Zitat von VanVan einholen
social: {},
page: "vanvan.html",
@@ -31,10 +35,12 @@ const MODIS = [
},
{
name: "Diene",
slug: "diene",
role: "Organisiert & plant gemeinsam mit VanVan",
rank: 2,
photo: "assets/img/card-diene.jpg",
avatar: "assets/img/avatar-diene.jpg",
bio: "Diene organisiert und plant gemeinsam mit VanVan den Alltag von Team Dogi. Zusammen sorgen die beiden dafür, dass Aufgaben ankommen, nichts liegen bleibt und das Team reibungslos zusammenarbeitet.",
quote: "",
social: {},
page: "",
@@ -42,10 +48,12 @@ const MODIS = [
},
{
name: "Funny",
slug: "funny",
role: "Modi",
rank: 3,
photo: "assets/img/card-funny.jpg",
avatar: "assets/img/avatar-funny.jpg",
bio: "Funny ist Teil des Modi-Teams von Team Dogi und mit dafür verantwortlich, dass im Chat und in der Community alles rundläuft.",
quote: "",
social: {},
page: "",
@@ -53,10 +61,12 @@ const MODIS = [
},
{
name: "Miss",
slug: "miss",
role: "Modi & Cutterin",
rank: 4,
photo: "assets/img/card-miss.jpg",
avatar: "assets/img/avatar-miss.jpg",
bio: "Miss ist als Modi im Chat aktiv und schneidet außerdem Clips aus den Streams — ein Teil der besten Momente von Dogfather geht direkt auf ihre Arbeit zurück.",
quote: "",
social: {},
page: "",
@@ -64,10 +74,12 @@ const MODIS = [
},
{
name: "Marina",
slug: "marina",
role: "Kommentare auf den Clip-Accounts · alias FlauschiRex",
rank: 5,
photo: "assets/img/card-marina.jpg",
avatar: "assets/img/avatar-marina.jpg",
bio: "Marina, alias FlauschiRex, kümmert sich um die Kommentare auf den Clip-Accounts von Team Dogi und sorgt dafür, dass die Community dort genauso gut betreut ist wie im Livestream.",
quote: "",
social: {},
page: "",
@@ -75,10 +87,12 @@ const MODIS = [
},
{
name: "Michi",
slug: "michi",
role: "Kommentare auf den Clip-Accounts",
rank: 5,
photo: "assets/img/card-michi.jpg",
avatar: "assets/img/avatar-michi.jpg",
bio: "Michi kümmert sich gemeinsam mit Marina um die Kommentare auf den Clip-Accounts von Team Dogi und ist Teil des Teams hinter den Kulissen.",
quote: "",
social: {},
page: "",
@@ -86,12 +100,14 @@ const MODIS = [
},
{
name: "Ghost",
slug: "ghost",
role: "Privat für Dogi — nicht für Stream-Themen zuständig",
// Auf dem Bild sind Ghost und seine Frau zu sehen (von Dogi ausdrücklich
// freigegeben). Gezählt und vorgestellt wird hier ausschließlich Ghost.
rank: 6,
photo: "assets/img/card-ghost.jpg",
avatar: "assets/img/avatar-ghost.jpg",
bio: "Ghost ist Teil von Team Dogi, aber privat für Dogi da — nicht für Stream- oder Community-Themen zuständig.",
quote: "",
social: {},
page: "",
+6
View File
@@ -9,8 +9,10 @@
Felder:
name Anzeigename (Groß-/Kleinschreibung korrekt!)
slug eindeutig, für die Profilseite (profil.html?p=slug)
role Kurzrolle / Fokus
photo Pfad zu assets/img/... oder "" für Platzhalter-Avatar
bio Vorstellungstext für die Profilseite
quote kurzes Zitat/Motto (optional, "" wenn keins)
social { platform: "url", ... } optional
status "aktiv" | "neu" | "pausiert"
@@ -19,18 +21,22 @@
const SCOUTS = [
{
name: "Patrick",
slug: "patrick",
role: "Scout — Team Dogi",
photo: "assets/img/avatar-patrick.jpg",
avatar: "assets/img/avatar-patrick.jpg",
bio: "Patrick ist Scout bei Team Dogi. Er findet und begleitet gemeinsam mit Bananenstift neue Talente für Dogfather als Manager.",
quote: "", // TODO: Zitat von Patrick einholen
social: {},
status: "aktiv",
},
{
name: "Bananenstift",
slug: "bananenstift",
role: "Scout — Team Dogi",
photo: "assets/img/avatar-bananenstift.jpg",
avatar: "assets/img/avatar-bananenstift.jpg",
bio: "Bananenstift ist Scout bei Team Dogi. Er findet und begleitet neue Talente für Dogfather als Manager.",
quote: "", // TODO: Zitat von Bananenstift einholen
social: {},
status: "aktiv",
+3 -2
View File
@@ -68,12 +68,13 @@ document.addEventListener("DOMContentLoaded", () => {
function draw(filter) {
const items = filter === "alle" ? list : list.filter((c) => c.category === filter);
grid.innerHTML = items.map((c) => `
<div class="profile-card">
<a class="profile-card" href="profil.html?p=${c.slug}">
<div class="avatar">${(c.avatar || c.photo) ? `<img src="${c.avatar || c.photo}" alt="${c.name}" loading="lazy">` : `<span class="avatar-initial">${c.name.charAt(0).toUpperCase()}</span>`}</div>
<h3>${c.name}</h3>
<div class="role">${c.role}</div>
${c.quote ? `<p class="quote">„${c.quote}"</p>` : ""}
</div>`).join("");
<span class="btn btn-outline" style="margin-top:.7rem;pointer-events:none;">Profil ansehen</span>
</a>`).join("");
}
filterBar.addEventListener("click", (e) => {
+2 -2
View File
@@ -322,11 +322,11 @@ document.addEventListener("DOMContentLoaded", () => {
teamPreview.innerHTML = people
.map(
(p) => `
<div class="profile-card">
<a class="profile-card" href="${p.page || `profil.html?p=${p.slug}`}">
<div class="avatar">${(p.avatar || p.photo) ? `<img src="${p.avatar || p.photo}" alt="${p.name}" loading="lazy">` : `<span class="avatar-initial">${p.name.charAt(0).toUpperCase()}</span>`}</div>
<h3>${p.name}</h3>
<div class="role">${p.role}</div>
</div>`
</a>`
)
.join("");
}
+179
View File
@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="de" data-theme="dogfather">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Profil — DOGFATHER UNIVERSE</title>
<meta name="description" content="Team Dogi — persönliche Vorstellung." />
<link rel="icon" type="image/png" href="assets/img/favicon.png" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="DOGFATHER UNIVERSE" />
<meta property="og:title" content="Profil — DOGFATHER UNIVERSE" />
<meta property="og:description" content="Team Dogi — persönliche Vorstellung." />
<meta property="og:image" content="https://REPLACE-WITH-YOUR-DOMAIN.tld/assets/img/og-cover.jpg" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="robots" content="noindex" /> <!-- generische URL, echte Team-Seiten verlinken hierher -->
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="assets/css/theme-dogfather.css" />
<link rel="stylesheet" href="assets/css/theme-hasidog.css" />
<link rel="stylesheet" href="assets/css/theme-spicymedia.css" />
<style>
.profil-photo { aspect-ratio: 4/5; max-width: 420px; margin: 0 auto; }
.profil-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.profil-badge { display: none; margin-bottom: .6rem; }
.profil-role { color: var(--accent); font-weight: 600; margin-bottom: 1.1rem; }
.profil-bio { font-size: 1.05rem; line-height: 1.7; }
.profil-quote { display: none; font-style: italic; font-size: 1.05rem; margin-top: 1.2rem; color: var(--text-muted); }
.profil-social { margin-top: 1.4rem; }
@media (max-width: 760px) { .profil-photo { max-width: 320px; } }
</style>
</head>
<body class="mit-hintergrund">
<div id="site-header"></div>
<main>
<section class="hero page-bg">
<div class="container">
<span class="eyebrow" id="profil-eyebrow">🩵 Team Dogi</span>
<h1 id="profil-hero-name">Profil</h1>
<p class="lead worlds" id="profil-hero-role"></p>
</div>
</section>
<div id="profil-root">
<section class="section-tight">
<div class="container grid grid-2" style="align-items:center;gap:2.6rem;">
<div class="collage-photo profil-photo" id="profil-photo-frame" style="border-radius:var(--radius);">
<img id="profil-img" src="" alt="" loading="lazy" />
</div>
<div>
<span class="badge rank-1 profil-badge" id="profil-badge"></span>
<h2 id="profil-name" style="margin-top:0;"></h2>
<div class="profil-role" id="profil-role"></div>
<p class="profil-bio" id="profil-bio"></p>
<p class="profil-quote" id="profil-quote"></p>
<div class="btn-row profil-social" id="profil-social"></div>
</div>
</div>
</section>
<section>
<div class="container text-center">
<a class="btn btn-outline" id="profil-back" href="index.html">← Zurück</a>
</div>
</section>
</div>
</main>
<div id="site-footer"></div>
<script src="assets/js/data-modis.js"></script>
<script src="assets/js/data-scouts.js"></script>
<script src="assets/js/data-creator.js"></script>
<script src="assets/js/main.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const slug = new URLSearchParams(location.search).get("p") || "";
const modis = window.MODIS || [];
const scouts = window.SCOUTS || [];
const creators = window.CREATORS || [];
const alle = [...modis, ...scouts, ...creators];
const person = alle.find((p) => p.slug === slug);
if (!person) {
document.getElementById("profil-root").innerHTML = `
<section class="section-tight">
<div class="container text-center">
<h2>Dieses Profil gibt's (noch) nicht 🤔</h2>
<p class="lead">Vielleicht ein alter Link — probier's über die Team-Seiten.</p>
<div class="btn-row" style="justify-content:center;">
<a class="btn btn-primary" href="team-modis.html">Zum Modi-Team</a>
<a class="btn btn-outline" href="team-scouts.html">Zum Scout-Team</a>
</div>
</div>
</section>`;
return;
}
const istScout = scouts.includes(person);
const istCreator = creators.includes(person);
const welt = istScout || istCreator ? "spicymedia" : "dogfather";
document.documentElement.setAttribute("data-theme", welt);
// Passendes Weltmotiv im Hintergrund, wie auf den Team-Übersichtsseiten
const bgBasis = istScout || istCreator ? "bg-scouts" : "bg-dogfather";
document.body.style.setProperty("--page-bg", `url('/assets/img/${bgBasis}.jpg')`);
document.body.style.setProperty("--page-bg-mobile", `url('/assets/img/${bgBasis}-mobile.jpg')`);
document.title = `${person.name} — DOGFATHER UNIVERSE`;
const beschreibung = person.bio || person.role || person.name;
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) metaDesc.setAttribute("content", beschreibung);
document.getElementById("profil-eyebrow").textContent =
istScout ? "Manager Dogfather — Spicy Media" : istCreator ? "Manager Dogfather — Creator" : "🩵 Team Dogi";
document.getElementById("profil-hero-name").textContent = person.name;
document.getElementById("profil-hero-role").textContent = person.role || "";
// Rahmenfarbe abwechslungsreich, aber für dieselbe Person immer gleich
const farben = ["frame-lila", "frame-silver", "frame-gold", "frame-tuerkis", ""];
const idx = [...person.slug].reduce((s, c) => s + c.charCodeAt(0), 0) % farben.length;
document.getElementById("profil-photo-frame").classList.add(...[farben[idx]].filter(Boolean));
const bild = person.photo || person.avatar;
const img = document.getElementById("profil-img");
img.src = bild;
img.alt = person.name;
img.addEventListener(
"error",
() => {
document.getElementById("profil-photo-frame").innerHTML =
`<div class="img-placeholder" style="position:absolute;inset:0;">${person.name}<br><span class="small">Foto folgt</span></div>`;
},
{ once: true }
);
document.getElementById("profil-name").textContent = person.name;
document.getElementById("profil-role").textContent = person.role || "";
document.getElementById("profil-bio").textContent =
person.bio || `Mehr über ${person.name} folgt in Kürze. 🩵`;
if (person.rank) {
const badge = document.getElementById("profil-badge");
badge.textContent = `Stufe ${person.rank}`;
badge.style.display = "inline-block";
}
if (person.quote) {
const q = document.getElementById("profil-quote");
q.textContent = `${person.quote}"`;
q.style.display = "block";
}
const socialLabels = { tiktok: "TikTok", instagram: "Instagram", spotify: "Spotify", discord: "Discord" };
const socialWrap = document.getElementById("profil-social");
Object.entries(person.social || {}).forEach(([plattform, url]) => {
if (!url) return;
const a = document.createElement("a");
a.className = "btn btn-outline";
a.href = url;
a.target = "_blank";
a.rel = "noopener";
a.textContent = socialLabels[plattform] || plattform;
socialWrap.appendChild(a);
});
const zurueck = document.getElementById("profil-back");
if (istScout) {
zurueck.href = "team-scouts.html";
zurueck.textContent = "← Zurück zum Scout-Team";
} else if (istCreator) {
zurueck.href = "creator.html";
zurueck.textContent = "← Zurück zu den Creatorn";
} else {
zurueck.href = "team-modis.html";
zurueck.textContent = "← Zurück zum Modi-Team";
}
});
</script>
</body>
</html>
+3 -3
View File
@@ -68,13 +68,13 @@ document.addEventListener("DOMContentLoaded", () => {
if (!grid || !window.MODIS) return;
const rest = window.MODIS.filter((m) => m.name !== "VanVan").sort((a,b) => a.rank - b.rank);
grid.innerHTML = rest.map((m) => `
<div class="profile-card">
<a class="profile-card" href="${m.page || `profil.html?p=${m.slug}`}">
<div class="avatar">${(m.avatar || m.photo) ? `<img src="${m.avatar || m.photo}" alt="${m.name}" loading="lazy">` : `<span class="avatar-initial">${m.name.charAt(0).toUpperCase()}</span>`}</div>
<span class="badge${m.rank === 1 ? " rank-1" : ""}" style="margin-bottom:.5rem;">Stufe ${m.rank}</span>
<h3>${m.name}</h3>
<div class="role">${m.role}</div>
${m.page ? `<a class="btn btn-outline" style="margin-top:.7rem;" href="${m.page}">Profil ansehen</a>` : ""}
</div>`).join("");
<span class="btn btn-outline" style="margin-top:.7rem;pointer-events:none;">Profil ansehen</span>
</a>`).join("");
});
</script>
</body>
+3 -2
View File
@@ -53,12 +53,13 @@ document.addEventListener("DOMContentLoaded", () => {
const grid = document.getElementById("scouts-grid");
if (!grid || !window.SCOUTS) return;
grid.innerHTML = window.SCOUTS.map((s) => `
<div class="profile-card">
<a class="profile-card" href="profil.html?p=${s.slug}">
<div class="avatar">${(s.avatar || s.photo) ? `<img src="${s.avatar || s.photo}" alt="${s.name}" loading="lazy">` : `<span class="avatar-initial">${s.name.charAt(0).toUpperCase()}</span>`}</div>
<h3>${s.name}</h3>
<div class="role">${s.role}</div>
${s.quote ? `<p class="quote">„${s.quote}"</p>` : `<p class="small muted">Zitat folgt</p>`}
</div>`).join("");
<span class="btn btn-outline" style="margin-top:.7rem;pointer-events:none;">Profil ansehen</span>
</a>`).join("");
});
</script>
</body>