DOGFATHER UNIVERSE: erste lauffähige Website + Bewerbungs-Postfach + Hosting-Vorbereitung

This commit is contained in:
2026-07-30 20:09:09 +02:00
commit f8e4cf82db
45 changed files with 3462 additions and 0 deletions
+450
View File
@@ -0,0 +1,450 @@
/* =====================================================================
DOGFATHER UNIVERSE — main.css
Gemeinsames Design-System für alle drei Welten (Dogfather / HasiDog /
Manager Dogfather). Farben kommen aus den theme-*.css Dateien über
CSS-Variablen — dieses File ist bewusst themen-neutral.
===================================================================== */
/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
img, svg { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; }
ul { list-style: none; margin: 0; padding: 0; }
/* ---------- Fallback-Theme (Dogfather) — wird von theme-*.css überschrieben ---------- */
:root {
--bg: #0b0d10;
--bg-alt: #12151a;
--card-bg: #171b21;
--text: #f3f5f7;
--text-muted: #9aa4b2;
--border: #262c35;
--accent: #8fd9ea; /* Babyblau */
--accent-2: #8b5cf6; /* Lila */
--accent-contrast: #0b0d10;
--radius: 14px;
--font-head: "Poppins", "Segoe UI", system-ui, sans-serif;
--font-body: "Inter", "Segoe UI", system-ui, sans-serif;
--shadow: 0 10px 30px rgba(0,0,0,.35);
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-body);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
h1, h2, h3, h4 {
font-family: var(--font-head);
line-height: 1.2;
margin: 0 0 .5em;
font-weight: 700;
}
h1 { font-size: clamp(2rem, 5vw, 3.2rem); }
h2 { font-size: clamp(1.5rem, 3.5vw, 2.3rem); }
h3 { font-size: 1.3rem; }
p { margin: 0 0 1em; color: var(--text-muted); }
.lead { font-size: 1.15rem; color: var(--text); }
.container {
width: min(1180px, 92vw);
margin: 0 auto;
}
section { padding: clamp(3rem, 6vw, 5.5rem) 0; }
.section-tight { padding: clamp(1.5rem, 3vw, 2.5rem) 0; }
.eyebrow {
display: inline-block;
font-size: .78rem;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--accent);
font-weight: 700;
margin-bottom: .8em;
}
/* ---------- Nebel/Partikel Hintergrund ---------- */
.fog-layer {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
overflow: hidden;
}
.fog-layer span {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, var(--accent) 0%, transparent 70%);
opacity: .08;
filter: blur(4px);
animation: drift 26s linear infinite;
}
@keyframes drift {
0% { transform: translateY(0) translateX(0); }
50% { transform: translateY(-40px) translateX(20px); }
100% { transform: translateY(0) translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
.fog-layer span { animation: none; }
}
/* ---------- Header / Navigation ---------- */
header.site-header {
position: sticky;
top: 0;
z-index: 50;
background: rgba(11,13,16,.82);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border);
}
.nav-bar {
width: min(1180px, 92vw);
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: .9rem 0;
}
.brand {
display: flex;
align-items: center;
gap: .6rem;
font-family: var(--font-head);
font-weight: 800;
font-size: 1.15rem;
letter-spacing: .02em;
}
.brand .heart { color: var(--accent); }
.brand small {
display: block;
font-size: .6rem;
font-weight: 500;
letter-spacing: .1em;
color: var(--text-muted);
text-transform: uppercase;
}
.nav-links {
display: flex;
align-items: center;
gap: 1.6rem;
}
.nav-group { position: relative; }
.nav-group > button {
background: none;
border: none;
color: var(--text);
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: .3rem;
padding: .5rem 0;
}
.nav-group > button:hover, .nav-group.open > button { color: var(--accent); }
.nav-flat { font-weight: 600; padding: .5rem 0; }
.nav-flat:hover, .nav-flat.active { color: var(--accent); }
.nav-dropdown {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
min-width: 220px;
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: .5rem;
box-shadow: var(--shadow);
display: none;
flex-direction: column;
}
.nav-group.open .nav-dropdown { display: flex; }
.nav-dropdown a {
padding: .55rem .7rem;
border-radius: 8px;
font-size: .92rem;
color: var(--text-muted);
}
.nav-dropdown a:hover, .nav-dropdown a.active { background: var(--bg-alt); color: var(--accent); }
.nav-cta {
padding: .55rem 1.1rem;
border-radius: 999px;
background: var(--accent);
color: var(--accent-contrast);
font-weight: 700;
font-size: .88rem;
}
.nav-toggle {
display: none;
background: none;
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
padding: .5rem .7rem;
cursor: pointer;
}
@media (max-width: 880px) {
.nav-toggle { display: block; }
.nav-links {
position: fixed;
inset: 62px 0 0 0;
background: var(--bg);
flex-direction: column;
align-items: stretch;
padding: 1.2rem 6vw;
gap: .3rem;
transform: translateY(-110%);
transition: transform .25s ease;
overflow-y: auto;
}
.nav-links.open { transform: translateY(0); }
.nav-group > button { width: 100%; justify-content: space-between; padding: .9rem 0; border-bottom: 1px solid var(--border); }
.nav-flat { display: block; padding: .9rem 0; border-bottom: 1px solid var(--border); }
.nav-dropdown { position: static; transform: none; box-shadow: none; width: 100%; }
.nav-group.open .nav-dropdown { display: flex; }
.nav-cta { align-self: flex-start; margin: 1rem 0; }
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
gap: .5rem;
padding: .8rem 1.5rem;
border-radius: 999px;
font-weight: 700;
font-size: .95rem;
border: 1px solid transparent;
cursor: pointer;
transition: transform .15s ease, opacity .15s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn-primary { background: var(--accent); color: var(--accent-contrast); }
.btn-secondary { background: var(--accent-2); color: #fff; }
.btn-outline { background: transparent; border-color: var(--border); color: var(--text); }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
.btn-row { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 1.5rem; }
/* ---------- Hero ---------- */
.hero {
position: relative;
padding: clamp(4rem, 10vw, 7rem) 0;
text-align: center;
}
.hero .worlds {
margin: .6rem 0 1.2rem;
color: var(--text-muted);
font-size: 1.05rem;
}
.hero-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.2rem;
margin-top: 3rem;
}
@media (max-width: 760px) { .hero-grid { grid-template-columns: 1fr; } }
/* ---------- Cards ---------- */
.card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.6rem;
box-shadow: var(--shadow);
height: 100%;
transition: border-color .15s ease, transform .15s ease;
}
.card:hover { border-color: var(--accent); transform: translateY(-3px); }
.card h3 { margin-bottom: .4rem; }
.card .tag {
display: inline-block;
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--accent);
border: 1px solid var(--accent);
border-radius: 999px;
padding: .15rem .6rem;
margin-bottom: .8rem;
}
.grid { display: grid; gap: 1.4rem; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 980px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }
/* ---------- Profil-Karten (Team/Creator) ---------- */
.profile-card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
text-align: center;
padding: 1.6rem 1.2rem;
}
.avatar {
width: 96px;
height: 96px;
border-radius: 50%;
margin: 0 auto .9rem;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-head);
font-weight: 800;
font-size: 1.6rem;
color: var(--accent-contrast);
overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.profile-card .role { color: var(--accent); font-weight: 600; font-size: .85rem; margin-bottom: .6rem; }
.profile-card .quote { font-style: italic; font-size: .9rem; }
.profile-card .socials { display: flex; justify-content: center; gap: .6rem; margin-top: .8rem; }
.profile-card .socials a {
border: 1px solid var(--border);
border-radius: 999px;
padding: .35rem .8rem;
font-size: .78rem;
color: var(--text-muted);
}
.profile-card .socials a:hover { color: var(--accent); border-color: var(--accent); }
/* ---------- Placeholder-Bild-Boxen (bis echte Fotos da sind) ---------- */
.img-placeholder {
aspect-ratio: 4 / 3;
border-radius: var(--radius);
background: repeating-linear-gradient(135deg, var(--bg-alt), var(--bg-alt) 10px, var(--card-bg) 10px, var(--card-bg) 20px);
border: 1px dashed var(--border);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: .8rem;
text-align: center;
padding: 1rem;
}
/* ---------- Timeline (Zeitreise) ---------- */
.timeline { position: relative; margin: 2rem 0; padding-left: 2rem; border-left: 2px solid var(--border); }
.timeline-item { position: relative; padding-bottom: 2.4rem; }
.timeline-item::before {
content: "";
position: absolute;
left: -2.45rem;
top: .2rem;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px var(--bg);
}
.timeline-item .date { color: var(--accent); font-weight: 700; font-size: .85rem; }
/* ---------- Gallery / Lightbox ---------- */
.filter-bar { display: flex; gap: .6rem; flex-wrap: wrap; margin-bottom: 1.6rem; }
.filter-btn {
border: 1px solid var(--border);
background: transparent;
color: var(--text-muted);
padding: .5rem 1rem;
border-radius: 999px;
cursor: pointer;
font-size: .85rem;
}
.filter-btn.active, .filter-btn:hover { border-color: var(--accent); color: var(--accent); }
.gallery-item { cursor: pointer; }
.lightbox {
position: fixed; inset: 0; background: rgba(0,0,0,.85);
display: none; align-items: center; justify-content: center; z-index: 200; padding: 5vh 5vw;
}
.lightbox.open { display: flex; }
.lightbox img, .lightbox .img-placeholder { max-height: 85vh; max-width: 90vw; }
.lightbox-close {
position: absolute; top: 20px; right: 30px; color: #fff; font-size: 2rem; cursor: pointer; background: none; border: none;
}
/* ---------- Formulare ---------- */
form.app-form { display: grid; gap: 1rem; max-width: 640px; }
.field { display: grid; gap: .4rem; }
.field label { font-weight: 600; font-size: .9rem; }
.field input, .field select, .field textarea {
background: var(--bg-alt);
border: 1px solid var(--border);
border-radius: 10px;
padding: .75rem .9rem;
color: var(--text);
font-family: var(--font-body);
}
.field textarea { min-height: 120px; resize: vertical; }
.field .error-msg { color: #ff6b6b; font-size: .8rem; display: none; }
.field.has-error input, .field.has-error select, .field.has-error textarea { border-color: #ff6b6b; }
.field.has-error .error-msg { display: block; }
.form-status { padding: .9rem 1.1rem; border-radius: 10px; font-size: .9rem; display: none; }
.form-status.show { display: block; }
.form-status.success { background: rgba(143,217,234,.12); border: 1px solid var(--accent); }
.form-status.error { background: rgba(255,107,107,.1); border: 1px solid #ff6b6b; }
.tabs { display: flex; gap: .6rem; margin-bottom: 1.8rem; flex-wrap: wrap; }
.tab-btn {
border: 1px solid var(--border); background: var(--card-bg); color: var(--text-muted);
padding: .7rem 1.3rem; border-radius: 999px; cursor: pointer; font-weight: 600;
}
.tab-btn.active { border-color: var(--accent); color: var(--accent); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
/* ---------- Footer ---------- */
footer.site-footer {
border-top: 1px solid var(--border);
padding: 3rem 0 2rem;
margin-top: 4rem;
position: relative;
z-index: 1;
}
.footer-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 2rem; }
@media (max-width: 760px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
.footer-grid h4 { font-size: .85rem; text-transform: uppercase; letter-spacing: .08em; color: var(--accent); }
.footer-grid ul { display: grid; gap: .5rem; }
.footer-grid a { color: var(--text-muted); font-size: .9rem; }
.footer-grid a:hover { color: var(--accent); }
.footer-bottom {
margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border);
display: flex; justify-content: space-between; flex-wrap: wrap; gap: .8rem;
font-size: .8rem; color: var(--text-muted);
}
/* ---------- Badges / Hierarchy ---------- */
.badge { display: inline-block; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; padding: .2rem .6rem; border-radius: 999px; background: var(--bg-alt); border: 1px solid var(--border); color: var(--text-muted); }
.badge.rank-1 { border-color: var(--accent); color: var(--accent); }
/* ---------- QR ---------- */
.qr-card { text-align: center; }
.qr-card .img-placeholder { width: 160px; height: 160px; margin: 0 auto 1rem; aspect-ratio: auto; }
/* ---------- Utility ---------- */
.text-center { text-align: center; }
.mt-0 { margin-top: 0; }
.muted { color: var(--text-muted); }
.small { font-size: .85rem; }
.todo-note {
border: 1px dashed var(--accent-2);
background: rgba(139,92,246,.08);
padding: 1rem 1.2rem;
border-radius: 10px;
font-size: .85rem;
color: var(--text-muted);
margin: 1.5rem 0;
}
.todo-note strong { color: var(--accent-2); }
+16
View File
@@ -0,0 +1,16 @@
/* =====================================================================
theme-dogfather.css — Welt 1: DOGFATHER (Streamer/Person)
Babyblau / Schwarz / Lila — die "Core"-Welt, sitzt auch als globaler
Default darunter (main.css Fallback = diese Werte).
===================================================================== */
:root[data-theme="dogfather"] {
--bg: #0a0c10;
--bg-alt: #12151b;
--card-bg: #171b22;
--text: #f4f6f8;
--text-muted: #9aa4b3;
--border: #262b35;
--accent: #8fd9ea; /* Babyblau — Markenfarbe */
--accent-2: #8b5cf6; /* Lila */
--accent-contrast: #0a0c10;
}
+15
View File
@@ -0,0 +1,15 @@
/* =====================================================================
theme-hasidog.css — Welt 2: HASIDOG (Community/Maskottchen/Kasper)
Babyblau / Silber / Nachtblau — verspielt, weicher, "Familie"-Gefühl.
===================================================================== */
:root[data-theme="hasidog"] {
--bg: #0d1420; /* Nachtblau */
--bg-alt: #131b29;
--card-bg: #182233;
--text: #f2f6fa;
--text-muted: #a9b6c9;
--border: #263248;
--accent: #8fd9ea; /* Babyblau */
--accent-2: #c7ccd1; /* Silber */
--accent-contrast: #0d1420;
}
+15
View File
@@ -0,0 +1,15 @@
/* =====================================================================
theme-spicymedia.css — Welt 3: MANAGER DOGFATHER (Spicy Media)
Schwarz / Rot / Orange — professionell, Agentur-Charakter.
===================================================================== */
:root[data-theme="spicymedia"] {
--bg: #0a0a0a;
--bg-alt: #131313;
--card-bg: #191717;
--text: #f6f5f4;
--text-muted: #ab9f9c;
--border: #2c2726;
--accent: #e0263f; /* Rot */
--accent-2: #ff7a1a; /* Orange */
--accent-contrast: #0a0a0a;
}
+70
View File
@@ -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;
+27
View File
@@ -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;
+94
View File
@@ -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;
+42
View File
@@ -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;
+129
View File
@@ -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");
});
+93
View File
@@ -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 &amp; 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);
+193
View File
@@ -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 &amp; 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 &amp; 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();
});
})();