This commit is contained in:
bonzei 2026-04-22 23:50:21 +02:00
parent 4558a1638b
commit b8e4f6c20b
16 changed files with 1324 additions and 317 deletions

View file

@ -15,10 +15,17 @@ const DATA_FILE = path.join(__dirname, 'data/gallery.json');
const HOMEPAGE_FILE = path.join(__dirname, 'data/homepage.json');
const CATEGORIES_FILE = path.join(__dirname, 'data/categories.json');
const UEBERMICH_FILE = path.join(__dirname, 'data/uebermich.json');
const ERFOLGE_FILE = path.join(__dirname, 'data/erfolge.json');
const ERFOLGE_IMG_DIR = path.join(__dirname, 'static/erfolge-img');
const GALERIE_PAGE_FILE = path.join(__dirname, 'data/galerie.json');
const GAESTEBUCH_FILE = path.join(__dirname, 'data/gaestebuch.json');
const GLOBAL_FILE = path.join(__dirname, 'data/global.json');
const GAESTEBUCH_IMG_DIR = path.join(__dirname, 'static/gaestebuch-img');
if (!fs.existsSync(UEBERMICH_IMG_DIR)) fs.mkdirSync(UEBERMICH_IMG_DIR, { recursive: true });
if (!fs.existsSync(HERO_DIR)) fs.mkdirSync(HERO_DIR, { recursive: true });
if (!fs.existsSync(ERFOLGE_IMG_DIR)) fs.mkdirSync(ERFOLGE_IMG_DIR, { recursive: true });
if (!fs.existsSync(GAESTEBUCH_IMG_DIR)) fs.mkdirSync(GAESTEBUCH_IMG_DIR, { recursive: true });
app.use(express.json());
app.use('/images', express.static(IMAGES_DIR));
@ -164,9 +171,14 @@ function rebuildAndDeploy() {
app.put('/api/homepage', (req, res) => {
const hp = readHomepage();
if (req.body.siteTitle !== undefined) hp.siteTitle = req.body.siteTitle;
if (req.body.badge !== undefined) hp.hero.badge = req.body.badge;
if (req.body.description !== undefined) hp.hero.description = req.body.description;
if (req.body.stats !== undefined) hp.stats = req.body.stats;
if (req.body.hero_karte !== undefined) hp.hero_karte = req.body.hero_karte;
if (req.body.pruefung_karte !== undefined) hp.pruefung_karte = req.body.pruefung_karte;
if (req.body.dojo_karte !== undefined) hp.dojo_karte = req.body.dojo_karte;
if (req.body.cta !== undefined) hp.cta = req.body.cta;
writeHomepage(hp);
res.json({ ok: true });
rebuildAndDeploy();
@ -191,6 +203,8 @@ app.post('/api/homepage/image', upload.single('image'), async (req, res) => {
app.use('/hero', express.static(HERO_DIR));
app.use('/uebermich', express.static(UEBERMICH_IMG_DIR));
app.use('/erfolge-img', express.static(ERFOLGE_IMG_DIR));
app.use('/gaestebuch-img', express.static(GAESTEBUCH_IMG_DIR));
// Über mich API
function readUebermich() {
@ -228,6 +242,87 @@ app.post('/api/uebermich/image', upload.single('image'), async (req, res) => {
}
});
// ── Erfolge API ───────────────────────────────────────────────
function readErfolge() {
return JSON.parse(fs.readFileSync(ERFOLGE_FILE, 'utf8'));
}
function writeErfolge(data) {
fs.writeFileSync(ERFOLGE_FILE, JSON.stringify(data, null, 2));
}
app.get('/api/erfolge', (req, res) => {
res.json(readErfolge());
});
app.put('/api/erfolge', (req, res) => {
writeErfolge(req.body);
res.json({ ok: true });
rebuildAndDeploy();
});
app.post('/api/erfolge/image', upload.single('image'), async (req, res) => {
try {
const filename = 'hero.webp';
await sharp(req.file.buffer)
.resize(1200, 1500, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 88 })
.toFile(path.join(ERFOLGE_IMG_DIR, filename));
const data = readErfolge();
data.hero.image = filename;
writeErfolge(data);
res.json({ ok: true, image: filename });
rebuildAndDeploy();
} catch (e) {
res.status(500).json({ ok: false, error: e.message });
}
});
// ── Galerie Seiten-Texte API ──────────────────────────────────
app.get('/api/galerie', (req, res) => {
res.json(JSON.parse(fs.readFileSync(GALERIE_PAGE_FILE, 'utf8')));
});
app.put('/api/galerie', (req, res) => {
fs.writeFileSync(GALERIE_PAGE_FILE, JSON.stringify(req.body, null, 2));
res.json({ ok: true });
rebuildAndDeploy();
});
// ── Gästebuch API ─────────────────────────────────────────────
app.get('/api/gaestebuch', (req, res) => {
res.json(JSON.parse(fs.readFileSync(GAESTEBUCH_FILE, 'utf8')));
});
app.put('/api/gaestebuch', (req, res) => {
fs.writeFileSync(GAESTEBUCH_FILE, JSON.stringify(req.body, null, 2));
res.json({ ok: true });
rebuildAndDeploy();
});
app.post('/api/gaestebuch/image', upload.single('image'), async (req, res) => {
try {
const filename = 'hero.webp';
await sharp(req.file.buffer)
.resize(1400, 600, { fit: 'cover', position: 'center' })
.webp({ quality: 88 })
.toFile(path.join(GAESTEBUCH_IMG_DIR, filename));
const data = JSON.parse(fs.readFileSync(GAESTEBUCH_FILE, 'utf8'));
data.hero.image = filename;
fs.writeFileSync(GAESTEBUCH_FILE, JSON.stringify(data, null, 2));
res.json({ ok: true, image: filename });
rebuildAndDeploy();
} catch (e) {
res.status(500).json({ ok: false, error: e.message });
}
});
// ── Global (Social Links) API ─────────────────────────────────
app.get('/api/global', (req, res) => {
res.json(JSON.parse(fs.readFileSync(GLOBAL_FILE, 'utf8')));
});
app.put('/api/global', (req, res) => {
fs.writeFileSync(GLOBAL_FILE, JSON.stringify(req.body, null, 2));
res.json({ ok: true });
rebuildAndDeploy();
});
// Admin-UI
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'admin.html'));

View file

@ -103,7 +103,7 @@ tailwind.config = {
<span class="material-symbols-outlined" style="font-variation-settings:'FILL' 1;">bolt</span>
</div>
<div>
<h1 class="text-xl font-black text-primary font-lexend">MiyaKarate</h1>
<h1 class="text-xl font-black text-primary font-lexend" id="adminSiteTitle">MiyaKarate</h1>
<p class="text-[10px] uppercase tracking-widest text-zinc-500 font-bold">Admin Panel</p>
</div>
</div>
@ -178,7 +178,7 @@ tailwind.config = {
</button>
<div class="flex items-center gap-3 bg-surface-container-low px-4 py-2 rounded-full">
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-primary to-primary-container flex items-center justify-center text-white text-xs font-black">M</div>
<span class="font-lexend font-bold text-sm">MiyaKarate</span>
<span class="font-lexend font-bold text-sm" id="adminSiteTitleHeader">MiyaKarate</span>
</div>
</div>
</header>
@ -265,6 +265,46 @@ tailwind.config = {
<!-- ══ SECTION: MEDIA LIBRARY ══ -->
<div id="section-media" class="hidden">
<!-- Galerie Seiten-Texte -->
<section class="bg-surface-container-lowest rounded-xl p-8 mb-8 shadow-sm">
<h3 class="text-xl font-black font-lexend text-on-surface mb-8 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">text_fields</span>
Galerie Seiten-Texte
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Badge (oben)</label>
<input id="galBadge" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (normal)</label>
<input id="galHeadingMain" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold uppercase"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (farbig)</label>
<input id="galHeadingColored" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold uppercase"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibungstext</label>
<textarea id="galDescription" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Stats Ribbon (Zähler unter dem Grid)</h4>
<div class="mb-4">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift Ribbon</label>
<input id="galRibbonHeading" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold"/>
</div>
<div class="mb-6">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibung Ribbon</label>
<textarea id="galRibbonDesc" rows="2" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-3">Statistiken (Wert + Bezeichnung)</label>
<div class="space-y-3 mb-8" id="galStatsFields"></div>
<button id="galSaveBtn" type="button" class="w-full py-4 rounded-xl font-black text-lg bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-lg shadow-primary/20 active:scale-95 transition-all font-lexend">
Galerie-Texte speichern & deployen
</button>
</section>
<!-- Upload -->
<section class="bg-surface-container-lowest rounded-xl p-8 mb-8 shadow-sm">
<h3 class="text-xl font-black font-lexend text-on-surface mb-6 flex items-center gap-2">
@ -382,6 +422,66 @@ tailwind.config = {
<div class="space-y-3" id="statsFields"></div>
</div>
<!-- Hero-Karte -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Hero-Karte (Rang auf dem Bild)</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Rang (z.B. Blaugurt)</label>
<input id="hpKarteRang" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Status (z.B. Status 2024)</label>
<input id="hpKarteStatus" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
</div>
<!-- Prüfungs-Karte -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Prüfungs-Karte (pinke Box)</h4>
<div class="grid grid-cols-1 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Titel</label>
<input id="hpPruefungTitel" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibung</label>
<textarea id="hpPruefungDesc" rows="2" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<!-- Dojo-Karte -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Dojo-Karte (graue Box)</h4>
<div class="grid grid-cols-1 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Titel</label>
<input id="hpDojoTitel" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibung</label>
<textarea id="hpDojoDesc" rows="2" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<!-- CTA-Sektion -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Galerie-CTA (dunkler Bereich)</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (normal)</label>
<input id="hpCtaMain" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (farbig/akzent)</label>
<input id="hpCtaColored" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibungstext</label>
<textarea id="hpCtaDesc" rows="2" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Button-Text</label>
<input id="hpCtaBtn" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold uppercase tracking-widest"/>
</div>
</div>
<button id="heroSaveBtn"
class="w-full py-4 rounded-xl font-black text-lg bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-lg shadow-primary/20 active:scale-95 transition-all font-lexend">
Änderungen speichern
@ -459,42 +559,6 @@ tailwind.config = {
</select>
</div>
<!-- Haupterfolg -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Haupterfolg</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Titel</label>
<input id="umHeTitel" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Platz / Ergebnis</label>
<input id="umHePlatz" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Jahr</label>
<input id="umHeJahr" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Ort</label>
<input id="umHeOrt" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Kategorie</label>
<input id="umHeKat" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibung</label>
<textarea id="umHeDesc" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<!-- Weitere Erfolge -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Weitere Erfolge</h4>
<div id="umWeitereFields" class="space-y-3 mb-3"></div>
<button id="umWeitereAdd" type="button" class="flex items-center gap-2 text-sm font-bold text-primary border border-primary/30 rounded-xl px-4 py-2 hover:bg-pink-50 transition-all mb-6">
<span class="material-symbols-outlined text-base">add</span> Erfolg hinzufügen
</button>
<!-- Stats -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Statistik-Boxen</h4>
<div id="umStatsFields" class="space-y-3 mb-6"></div>
@ -517,22 +581,245 @@ tailwind.config = {
<!-- ══ SECTION: CAREER ══ -->
<div id="section-career" class="hidden">
<div class="bg-surface-container-low rounded-xl p-12 text-center">
<span class="material-symbols-outlined text-6xl text-surface-variant mb-4 block">emoji_events</span>
<h3 class="text-xl font-black font-lexend text-on-surface-variant">Erfolge — demnächst verfügbar</h3>
<section class="bg-surface-container-lowest rounded-xl p-8 shadow-sm">
<h3 class="text-xl font-black font-lexend text-on-surface mb-8 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">emoji_events</span>
Erfolge-Seite bearbeiten
</h3>
<!-- Hero-Bild -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Hero-Bild</h4>
<div class="flex gap-6 items-start flex-wrap mb-8">
<div class="w-40 h-48 rounded-xl overflow-hidden bg-surface-container flex-shrink-0">
<img id="efHeroPreview" src="" alt="Erfolge Hero" class="w-full h-full object-cover hidden"/>
<div id="efHeroPlaceholder" class="w-full h-full flex flex-col items-center justify-center text-on-surface-variant">
<span class="material-symbols-outlined text-4xl mb-2">image</span>
<span class="text-xs font-medium">Kein Bild</span>
</div>
</div>
<div class="flex-1 min-w-48">
<label for="efHeroFileInput" class="block border-2 border-dashed border-primary/30 bg-surface-container-low rounded-xl p-6 text-center cursor-pointer hover:border-primary/60 transition-all mb-3">
<span class="material-symbols-outlined text-primary block mb-2">upload_file</span>
<p class="text-sm font-bold text-primary font-lexend">Bild auswählen</p>
<p class="text-xs text-on-surface-variant mt-1">JPG, PNG, HEIC</p>
</label>
<input type="file" id="efHeroFileInput" accept="image/*" class="sr-only"/>
<button id="efHeroUploadBtn" disabled class="w-full py-3 rounded-xl font-bold text-sm bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-md shadow-primary/20 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95 transition-all font-lexend">Bild hochladen</button>
<p id="efHeroUploadStatus" class="text-xs text-on-surface-variant mt-2 text-center"></p>
</div>
</div>
<!-- Hero-Texte -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Hero-Text</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Badge-Text</label>
<input id="efBadge" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Aktueller Rang (Karte)</label>
<input id="efRang" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (normal)</label>
<input id="efHeadingMain" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-black uppercase"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (farbig)</label>
<input id="efHeadingColored" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-black uppercase"/>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibungstext</label>
<textarea id="efDescription" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm leading-relaxed resize-none"></textarea>
</div>
</div>
<!-- Haupt-Meilenstein -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Haupt-Meilenstein (große Karte)</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Event / Titel</label>
<input id="efMeilensteinEvent" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Platz / Ergebnis</label>
<input id="efMeilensteinPlatz" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Jahr</label>
<input id="efMeilensteinJahr" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Ort</label>
<input id="efMeilensteinOrt" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Kategorie</label>
<input id="efMeilensteinKat" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibung</label>
<textarea id="efMeilensteinDesc" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<!-- Weitere Auszeichnungen -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Weitere Auszeichnungen</h4>
<div id="efWeitereFields" class="space-y-3 mb-3"></div>
<button id="efWeitereAdd" type="button" class="flex items-center gap-2 text-sm font-bold text-primary border border-primary/30 rounded-xl px-4 py-2 hover:bg-pink-50 transition-all mb-8">
<span class="material-symbols-outlined text-base">add</span> Auszeichnung hinzufügen
</button>
<!-- Statistiken -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-2">Statistiken (3 Boxen)</h4>
<p class="text-xs text-on-surface-variant mb-4">Box 1 = lila, Box 2 = grau, Box 3 = weiß mit pinker Linie.</p>
<div id="efStatsFields" class="space-y-3 mb-8"></div>
<!-- Zitat -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-4">Zitat</h4>
<div class="mb-4">
<textarea id="efZitatText" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
<div class="mb-8">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Autor</label>
<input id="efZitatAutor" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<button id="careerSaveBtn" class="w-full py-4 rounded-xl font-black text-lg bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-lg shadow-primary/20 active:scale-95 transition-all font-lexend">
Erfolge speichern & deployen
</button>
</section>
</div>
<!-- ══ SECTION: COMMUNITY ══ -->
<div id="section-community" class="hidden">
<div class="bg-surface-container-low rounded-xl p-12 text-center">
<span class="material-symbols-outlined text-6xl text-surface-variant mb-4 block">forum</span>
<h3 class="text-xl font-black font-lexend text-on-surface-variant">Gästebuch — demnächst verfügbar</h3>
<section class="bg-surface-container-lowest rounded-xl p-8 shadow-sm mb-8">
<h3 class="text-xl font-black font-lexend text-on-surface mb-8 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">forum</span>
Gästebuch bearbeiten
</h3>
<!-- Hero Bild -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Hero-Bild (Hintergrundbild)</h4>
<div class="flex gap-6 items-start flex-wrap mb-8">
<div class="w-48 h-24 rounded-xl overflow-hidden bg-surface-container flex-shrink-0">
<img id="gbHeroPreview" src="" alt="Gästebuch Hero" class="w-full h-full object-cover hidden"/>
<div id="gbHeroPlaceholder" class="w-full h-full flex flex-col items-center justify-center text-on-surface-variant">
<span class="material-symbols-outlined text-3xl mb-1">image</span>
<span class="text-xs font-medium">Kein Bild</span>
</div>
</div>
<div class="flex-1 min-w-48">
<label for="gbHeroFileInput" class="block border-2 border-dashed border-primary/30 bg-surface-container-low rounded-xl p-5 text-center cursor-pointer hover:border-primary/60 transition-all mb-3">
<span class="material-symbols-outlined text-primary block mb-1">upload_file</span>
<p class="text-sm font-bold text-primary font-lexend">Bild auswählen</p>
<p class="text-xs text-on-surface-variant mt-1">JPG, PNG, HEIC (16:9 empfohlen)</p>
</label>
<input type="file" id="gbHeroFileInput" accept="image/*" class="sr-only"/>
<button id="gbHeroUploadBtn" disabled class="w-full py-3 rounded-xl font-bold text-sm bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-md shadow-primary/20 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95 transition-all font-lexend">Bild hochladen</button>
<p id="gbHeroUploadStatus" class="text-xs text-on-surface-variant mt-2 text-center"></p>
</div>
</div>
<!-- Hero Texte -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Hero-Text</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Badge</label>
<input id="gbBadge" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (normal)</label>
<input id="gbHeading" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Überschrift (farbig)</label>
<input id="gbHeadingColored" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibungstext</label>
<textarea id="gbDescription" rows="3" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none"></textarea>
</div>
</div>
<!-- Kontakt -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mb-4">Kontakt-Info (Sidebar)</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">E-Mail</label>
<input id="gbEmail" type="email" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div>
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Dojo-Name</label>
<input id="gbDojo" type="text" class="w-full bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
</div>
<!-- Einträge -->
<h4 class="text-sm font-black font-lexend text-on-surface-variant uppercase tracking-widest mt-8 mb-2">Gästebuch-Einträge</h4>
<p class="text-xs text-on-surface-variant mb-4">Erster Eintrag erscheint im "Neuester Eintrag"-Widget. Farbe "Akzent" erzeugt die lila/pink Verlaufskarte.</p>
<div id="gbEintragFields" class="space-y-3 mb-3"></div>
<button id="gbEintragAdd" type="button" class="flex items-center gap-2 text-sm font-bold text-primary border border-primary/30 rounded-xl px-4 py-2 hover:bg-pink-50 transition-all mb-8">
<span class="material-symbols-outlined text-base">add</span> Eintrag hinzufügen
</button>
<button id="gbSaveBtn" class="w-full py-4 rounded-xl font-black text-lg bg-gradient-to-r from-primary to-primary-container text-on-primary shadow-lg shadow-primary/20 active:scale-95 transition-all font-lexend">
Gästebuch speichern & deployen
</button>
</section>
</div>
<!-- ══ SECTION: SETTINGS ══ -->
<div id="section-settings" class="hidden">
<!-- Social Links -->
<section class="bg-surface-container-lowest rounded-xl p-8 shadow-sm mb-6">
<h3 class="text-xl font-black font-lexend text-on-surface mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">share</span>
Social Links
</h3>
<p class="text-sm text-on-surface-variant mb-6">Diese Links erscheinen im Footer auf allen Seiten der Website.</p>
<div class="space-y-4 mb-6">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-on-surface-variant">photo_camera</span>
<input id="socialInstagram" type="text" placeholder="Instagram URL (oder #)"
class="flex-1 bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-on-surface-variant">smart_display</span>
<input id="socialYoutube" type="text" placeholder="YouTube URL (oder #)"
class="flex-1 bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-on-surface-variant">mail</span>
<input id="socialEmail" type="email" placeholder="E-Mail-Adresse"
class="flex-1 bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>
</div>
</div>
<button type="button" id="saveSocialBtn"
class="px-6 py-3 rounded-xl font-black text-sm bg-primary text-on-primary active:scale-95 transition-all font-lexend flex items-center gap-2">
<span class="material-symbols-outlined text-sm">save</span> Speichern & deployen
</button>
<p id="socialStatus" class="text-xs text-on-surface-variant mt-2"></p>
</section>
<!-- Site-Name -->
<section class="bg-surface-container-lowest rounded-xl p-8 shadow-sm mb-6">
<h3 class="text-xl font-black font-lexend text-on-surface mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">badge</span>
Site-Name
</h3>
<p class="text-sm text-on-surface-variant mb-4">Dieser Name erscheint in der Navbar, im Footer und überall auf der Website.</p>
<div class="flex gap-3">
<input id="siteTitleInput" type="text" placeholder="z.B. MiyaKarate"
class="flex-1 bg-surface-container-low rounded-DEFAULT px-4 py-3 text-on-surface placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold font-lexend uppercase italic tracking-tight"/>
<button type="button" id="saveSiteTitleBtn"
class="px-6 py-3 rounded-xl font-black text-sm bg-primary text-on-primary active:scale-95 transition-all font-lexend flex items-center gap-2">
<span class="material-symbols-outlined text-sm">save</span> Speichern
</button>
</div>
<p id="siteTitleStatus" class="text-xs text-on-surface-variant mt-2"></p>
</section>
<section class="bg-surface-container-lowest rounded-xl p-8 shadow-sm">
<h3 class="text-xl font-black font-lexend text-on-surface mb-6 flex items-center gap-2">
<span class="material-symbols-outlined text-primary">label</span>
@ -634,10 +921,12 @@ tailwind.config = {
const [title, sub] = pageTitles[name] || ['Admin', ''];
document.getElementById('pageTitle').textContent = title;
document.getElementById('pageSubtitle').textContent = sub;
if (name === 'media') loadPhotos();
if (name === 'media') { loadPhotos(); loadGaleriePage(); }
if (name === 'homepage') loadHomepage();
if (name === 'uebermich') loadUebermich();
if (name === 'settings') renderCatManage();
if (name === 'career') loadCareer();
if (name === 'community') loadGaestebuch();
if (name === 'settings') { renderCatManage(); loadSiteTitle(); loadSocialLinks(); }
}
document.getElementById('sideNav').addEventListener('click', function (e) {
@ -1179,15 +1468,29 @@ tailwind.config = {
const hero = data.hero || {};
document.getElementById('heroBadge').value = hero.badge || '';
document.getElementById('heroDescription').value = hero.description || '';
const hk = data.hero_karte || {};
document.getElementById('hpKarteRang').value = hk.rang || '';
document.getElementById('hpKarteStatus').value = hk.status || '';
const pk = data.pruefung_karte || {};
document.getElementById('hpPruefungTitel').value = pk.titel || '';
document.getElementById('hpPruefungDesc').value = pk.beschreibung || '';
const dk = data.dojo_karte || {};
document.getElementById('hpDojoTitel').value = dk.titel || '';
document.getElementById('hpDojoDesc').value = dk.beschreibung || '';
const cta = data.cta || {};
document.getElementById('hpCtaMain').value = cta.heading_main || '';
document.getElementById('hpCtaColored').value = cta.heading_colored || '';
document.getElementById('hpCtaDesc').value = cta.description || '';
document.getElementById('hpCtaBtn').value = cta.button_text || '';
const statsFields = document.getElementById('statsFields');
statsFields.innerHTML = '';
(data.stats || []).forEach(function(s) {
const row = document.createElement('div');
row.className = 'flex gap-3 items-center';
row.innerHTML =
'<input type="text" value="' + s.value + '" placeholder="Wert" data-stat="value"' +
'<input type="text" value="' + escHtml(s.value||'') + '" placeholder="Wert" data-stat="value"' +
' class="w-24 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-black text-center"/>' +
'<input type="text" value="' + s.label + '" placeholder="Bezeichnung" data-stat="label"' +
'<input type="text" value="' + escHtml(s.label||'') + '" placeholder="Bezeichnung" data-stat="label"' +
' class="flex-1 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>';
statsFields.appendChild(row);
});
@ -1248,10 +1551,28 @@ tailwind.config = {
body: JSON.stringify({
badge: document.getElementById('heroBadge').value,
description: document.getElementById('heroDescription').value,
stats: stats
stats: stats,
hero_karte: {
rang: document.getElementById('hpKarteRang').value,
status: document.getElementById('hpKarteStatus').value
},
pruefung_karte: {
titel: document.getElementById('hpPruefungTitel').value,
beschreibung: document.getElementById('hpPruefungDesc').value
},
dojo_karte: {
titel: document.getElementById('hpDojoTitel').value,
beschreibung: document.getElementById('hpDojoDesc').value
},
cta: {
heading_main: document.getElementById('hpCtaMain').value,
heading_colored: document.getElementById('hpCtaColored').value,
description: document.getElementById('hpCtaDesc').value,
button_text: document.getElementById('hpCtaBtn').value
}
})
});
if (r.ok) showToast('✓ Startseite gespeichert');
if (r.ok) showToast('✓ Startseite gespeichert & Deploy gestartet');
} catch (err) { console.error('Save error:', err); }
});
@ -1282,10 +1603,6 @@ tailwind.config = {
return row;
}
document.getElementById('umWeitereAdd').addEventListener('click', function() {
document.getElementById('umWeitereFields').appendChild(createWeitereRow('', ''));
});
async function loadUebermich() {
try {
const r = await fetch('/api/uebermich');
@ -1297,22 +1614,9 @@ tailwind.config = {
document.getElementById('umHSuffix').value = d.hero.heading_suffix || '';
document.getElementById('umDesc').value = d.hero.description || '';
document.getElementById('umGurt').value = d.gurtweg.aktiver_gurt || 'Blau';
document.getElementById('umHeTitel').value = d.haupterfolg.titel || '';
document.getElementById('umHePlatz').value = d.haupterfolg.platz || '';
document.getElementById('umHeJahr').value = d.haupterfolg.jahr || '';
document.getElementById('umHeOrt').value = d.haupterfolg.ort || '';
document.getElementById('umHeKat').value = d.haupterfolg.kategorie || '';
document.getElementById('umHeDesc').value = d.haupterfolg.beschreibung || '';
document.getElementById('umZitat').value = d.zitat.text || '';
document.getElementById('umZitatAutor').value = d.zitat.autor || '';
// Weitere Erfolge
const wf = document.getElementById('umWeitereFields');
wf.innerHTML = '';
(d.weitere_erfolge || []).forEach(function(e) {
wf.appendChild(createWeitereRow(e.titel, e.detail, e.beschreibung, e.jahr, e.ort, e.kategorie));
});
// Stats
const sf = document.getElementById('umStatsFields');
sf.innerHTML = '';
@ -1378,27 +1682,9 @@ tailwind.config = {
data.hero.heading_suffix = document.getElementById('umHSuffix').value;
data.hero.description = document.getElementById('umDesc').value;
data.gurtweg.aktiver_gurt = document.getElementById('umGurt').value;
data.haupterfolg.titel = document.getElementById('umHeTitel').value;
data.haupterfolg.platz = document.getElementById('umHePlatz').value;
data.haupterfolg.jahr = document.getElementById('umHeJahr').value;
data.haupterfolg.ort = document.getElementById('umHeOrt').value;
data.haupterfolg.kategorie = document.getElementById('umHeKat').value;
data.haupterfolg.beschreibung = document.getElementById('umHeDesc').value;
data.zitat.text = document.getElementById('umZitat').value;
data.zitat.autor = document.getElementById('umZitatAutor').value;
const weRows = document.getElementById('umWeitereFields').querySelectorAll(':scope > div');
data.weitere_erfolge = Array.from(weRows).map(function(row) {
return {
titel: row.querySelector('[data-we="titel"]').value,
detail: row.querySelector('[data-we="detail"]').value,
beschreibung: row.querySelector('[data-we="beschreibung"]').value,
jahr: row.querySelector('[data-we="jahr"]').value,
ort: row.querySelector('[data-we="ort"]').value,
kategorie: row.querySelector('[data-we="kategorie"]').value
};
});
const stRows = document.getElementById('umStatsFields').querySelectorAll('div.flex');
data.stats = Array.from(stRows).map(function(row) {
return {
@ -1416,8 +1702,414 @@ tailwind.config = {
} catch(err) { console.error('Save error:', err); }
});
// ─── Erfolge (Career) ─────────────────────────────────────
function createAuszeichnungRow(titel, detail) {
const row = document.createElement('div');
row.className = 'bg-surface-container-low rounded-xl p-4 flex gap-3 items-center';
row.innerHTML =
'<input type="text" value="' + escHtml(titel||'') + '" placeholder="Titel" data-ea="titel"' +
' class="flex-1 bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold"/>' +
'<input type="text" value="' + escHtml(detail||'') + '" placeholder="Untertitel (z.B. Silber Kata 2024)" data-ea="detail"' +
' class="flex-1 bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>' +
'<button type="button" class="ea-delete shrink-0 w-8 h-8 flex items-center justify-center rounded-lg text-error hover:bg-error/10 transition-all">' +
'<span class="material-symbols-outlined text-lg">delete</span></button>';
row.querySelector('.ea-delete').addEventListener('click', function() { row.remove(); });
return row;
}
document.getElementById('efWeitereAdd').addEventListener('click', function() {
document.getElementById('efWeitereFields').appendChild(createAuszeichnungRow('', ''));
});
document.getElementById('efHeroFileInput').addEventListener('change', function() {
const file = this.files[0];
if (!file) return;
document.getElementById('efHeroPreview').src = URL.createObjectURL(file);
document.getElementById('efHeroPreview').classList.remove('hidden');
document.getElementById('efHeroPlaceholder').classList.add('hidden');
document.getElementById('efHeroUploadBtn').disabled = false;
document.getElementById('efHeroUploadStatus').textContent = file.name;
});
document.getElementById('efHeroUploadBtn').addEventListener('click', async function() {
const file = document.getElementById('efHeroFileInput').files[0];
if (!file) return;
this.disabled = true;
document.getElementById('efHeroUploadStatus').textContent = 'Wird hochgeladen…';
try {
const fd = new FormData();
fd.append('image', file);
const r = await fetch('/api/erfolge/image', { method: 'POST', body: fd });
if (r.ok) {
showToast('✓ Bild gespeichert');
document.getElementById('efHeroUploadStatus').textContent = 'Gespeichert!';
} else {
document.getElementById('efHeroUploadStatus').textContent = 'Fehler beim Upload';
}
} catch(err) {
document.getElementById('efHeroUploadStatus').textContent = 'Verbindungsfehler';
}
this.disabled = false;
});
async function loadCareer() {
try {
const r = await fetch('/api/erfolge');
const d = await r.json();
const hero = d.hero || {};
document.getElementById('efBadge').value = hero.badge || '';
document.getElementById('efHeadingMain').value = hero.heading_main || '';
document.getElementById('efHeadingColored').value = hero.heading_colored || '';
document.getElementById('efDescription').value = hero.description || '';
document.getElementById('efRang').value = hero.rang_value || '';
if (hero.image) {
document.getElementById('efHeroPreview').src = '/erfolge-img/' + hero.image + '?t=' + Date.now();
document.getElementById('efHeroPreview').classList.remove('hidden');
document.getElementById('efHeroPlaceholder').classList.add('hidden');
}
const m = d.meilenstein || {};
document.getElementById('efMeilensteinEvent').value = m.event || '';
document.getElementById('efMeilensteinPlatz').value = m.platz || '';
document.getElementById('efMeilensteinDesc').value = m.beschreibung || '';
document.getElementById('efMeilensteinJahr').value = m.jahr || '';
document.getElementById('efMeilensteinOrt').value = m.ort || '';
document.getElementById('efMeilensteinKat').value = m.kategorie || '';
const wf = document.getElementById('efWeitereFields');
wf.innerHTML = '';
(d.weitere_auszeichnungen || []).forEach(function(a) {
wf.appendChild(createAuszeichnungRow(a.titel, a.detail));
});
const sf = document.getElementById('efStatsFields');
sf.innerHTML = '';
(d.stats || []).forEach(function(s) {
const row = document.createElement('div');
row.className = 'flex gap-3 items-center';
row.innerHTML =
'<input type="text" value="' + escHtml(s.wert||'') + '" placeholder="Wert" data-es="wert"' +
' class="w-24 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-black text-center"/>' +
'<input type="text" value="' + escHtml(s.label||'') + '" placeholder="Bezeichnung" data-es="label"' +
' class="flex-1 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>';
sf.appendChild(row);
});
document.getElementById('efZitatText').value = (d.zitat || {}).text || '';
document.getElementById('efZitatAutor').value = (d.zitat || {}).autor || '';
} catch(err) { console.error('Career load error:', err); }
}
document.getElementById('careerSaveBtn').addEventListener('click', async function() {
try {
const r0 = await fetch('/api/erfolge');
const existing = await r0.json();
const weRows = document.getElementById('efWeitereFields').querySelectorAll(':scope > div');
const stRows = document.getElementById('efStatsFields').querySelectorAll('div.flex');
const data = {
hero: {
badge: document.getElementById('efBadge').value,
heading_main: document.getElementById('efHeadingMain').value,
heading_colored: document.getElementById('efHeadingColored').value,
description: document.getElementById('efDescription').value,
rang_value: document.getElementById('efRang').value,
rang_label: (existing.hero || {}).rang_label || 'Aktueller Rang',
image: (existing.hero || {}).image || ''
},
meilenstein: {
event: document.getElementById('efMeilensteinEvent').value,
platz: document.getElementById('efMeilensteinPlatz').value,
beschreibung: document.getElementById('efMeilensteinDesc').value,
jahr: document.getElementById('efMeilensteinJahr').value,
ort: document.getElementById('efMeilensteinOrt').value,
kategorie: document.getElementById('efMeilensteinKat').value
},
weitere_auszeichnungen: Array.from(weRows).map(function(row) {
return {
titel: row.querySelector('[data-ea="titel"]').value,
detail: row.querySelector('[data-ea="detail"]').value
};
}),
stats: Array.from(stRows).map(function(row) {
return {
wert: row.querySelector('[data-es="wert"]').value,
label: row.querySelector('[data-es="label"]').value
};
}),
zitat: {
text: document.getElementById('efZitatText').value,
autor: document.getElementById('efZitatAutor').value
}
};
const res = await fetch('/api/erfolge', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (res.ok) showToast('✓ Erfolge gespeichert & Deploy gestartet');
} catch(err) { console.error('Career save error:', err); }
});
// ─── Site-Name ────────────────────────────────────────────
async function loadSiteTitle() {
try {
const r = await fetch('/api/homepage');
const data = await r.json();
const title = data.siteTitle || '';
document.getElementById('siteTitleInput').value = title;
updateAdminTitle(title);
} catch (err) { console.error('SiteTitle load error:', err); }
}
function updateAdminTitle(title) {
if (!title) return;
document.getElementById('adminSiteTitle').textContent = title;
document.getElementById('adminSiteTitleHeader').textContent = title;
}
document.getElementById('saveSiteTitleBtn').addEventListener('click', async function () {
const status = document.getElementById('siteTitleStatus');
const val = document.getElementById('siteTitleInput').value.trim();
if (!val) { status.textContent = 'Bitte einen Namen eingeben.'; return; }
try {
const r = await fetch('/api/homepage', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ siteTitle: val })
});
if (r.ok) {
updateAdminTitle(val);
showToast('✓ Site-Name gespeichert & Deploy gestartet');
status.textContent = '';
} else {
status.textContent = 'Fehler beim Speichern.';
}
} catch (err) { status.textContent = 'Verbindungsfehler'; }
});
// ─── Galerie Seiten-Texte ─────────────────────────────────
async function loadGaleriePage() {
try {
const r = await fetch('/api/galerie');
const d = await r.json();
const hero = d.hero || {};
document.getElementById('galBadge').value = hero.badge || '';
document.getElementById('galHeadingMain').value = hero.heading_main || '';
document.getElementById('galHeadingColored').value = hero.heading_colored || '';
document.getElementById('galDescription').value = hero.description || '';
const ribbon = d.ribbon || {};
document.getElementById('galRibbonHeading').value = ribbon.heading || '';
document.getElementById('galRibbonDesc').value = ribbon.description || '';
const sf = document.getElementById('galStatsFields');
sf.innerHTML = '';
(ribbon.stats || []).forEach(function(s) {
const row = document.createElement('div');
row.className = 'flex gap-3 items-center';
row.innerHTML =
'<input type="text" value="' + escHtml(s.wert||'') + '" placeholder="Wert" data-gs="wert"' +
' class="w-24 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-black text-center"/>' +
'<input type="text" value="' + escHtml(s.label||'') + '" placeholder="Bezeichnung" data-gs="label"' +
' class="flex-1 bg-surface-container-low rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>';
sf.appendChild(row);
});
} catch(err) { console.error('Galerie page load error:', err); }
}
document.getElementById('galSaveBtn').addEventListener('click', async function() {
try {
const stRows = document.getElementById('galStatsFields').querySelectorAll('div.flex');
const data = {
hero: {
badge: document.getElementById('galBadge').value,
heading_main: document.getElementById('galHeadingMain').value,
heading_colored: document.getElementById('galHeadingColored').value,
description: document.getElementById('galDescription').value
},
ribbon: {
heading: document.getElementById('galRibbonHeading').value,
description: document.getElementById('galRibbonDesc').value,
stats: Array.from(stRows).map(function(row) {
return {
wert: row.querySelector('[data-gs="wert"]').value,
label: row.querySelector('[data-gs="label"]').value
};
})
}
};
const res = await fetch('/api/galerie', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (res.ok) showToast('✓ Galerie-Texte gespeichert & Deploy gestartet');
} catch(err) { console.error('Galerie save error:', err); }
});
// ─── Gästebuch ────────────────────────────────────────────
function createEintragRow(name, rolle, text, farbe, breit) {
const row = document.createElement('div');
row.className = 'bg-surface-container-low rounded-xl p-4 flex flex-col gap-3';
row.innerHTML =
'<div class="flex gap-3 items-start">' +
'<div class="flex-1 grid grid-cols-2 gap-3">' +
'<input type="text" value="' + escHtml(name||'') + '" placeholder="Name" data-gb="name"' +
' class="bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm font-bold"/>' +
'<input type="text" value="' + escHtml(rolle||'') + '" placeholder="Rolle (z.B. Trainerin)" data-gb="rolle"' +
' class="bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>' +
'</div>' +
'<button type="button" class="gb-delete shrink-0 w-8 h-8 flex items-center justify-center rounded-lg text-error hover:bg-error/10 transition-all">' +
'<span class="material-symbols-outlined text-lg">delete</span></button>' +
'</div>' +
'<textarea data-gb="text" rows="2" placeholder="Nachricht"' +
' class="w-full bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm resize-none">' + escHtml(text||'') + '</textarea>' +
'<div class="flex gap-4 items-center flex-wrap">' +
'<label class="flex items-center gap-2 text-sm cursor-pointer">' +
'<input type="checkbox" data-gb="breit" ' + (breit ? 'checked' : '') + ' class="rounded"/>' +
'<span class="text-on-surface-variant font-medium">Breite Karte</span>' +
'</label>' +
'<select data-gb="farbe" class="bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm">' +
'<option value="default"' + ((!farbe || farbe === 'default') ? ' selected' : '') + '>Standard (weiß)</option>' +
'<option value="secondary"' + (farbe === 'secondary' ? ' selected' : '') + '>Akzent (lila/pink)</option>' +
'</select>' +
'</div>';
row.querySelector('.gb-delete').addEventListener('click', function() { row.remove(); });
return row;
}
document.getElementById('gbEintragAdd').addEventListener('click', function() {
document.getElementById('gbEintragFields').appendChild(createEintragRow('', '', '', 'default', false));
});
document.getElementById('gbHeroFileInput').addEventListener('change', function() {
const file = this.files[0];
if (!file) return;
document.getElementById('gbHeroPreview').src = URL.createObjectURL(file);
document.getElementById('gbHeroPreview').classList.remove('hidden');
document.getElementById('gbHeroPlaceholder').classList.add('hidden');
document.getElementById('gbHeroUploadBtn').disabled = false;
document.getElementById('gbHeroUploadStatus').textContent = file.name;
});
document.getElementById('gbHeroUploadBtn').addEventListener('click', async function() {
const file = document.getElementById('gbHeroFileInput').files[0];
if (!file) return;
this.disabled = true;
document.getElementById('gbHeroUploadStatus').textContent = 'Wird hochgeladen…';
try {
const fd = new FormData();
fd.append('image', file);
const r = await fetch('/api/gaestebuch/image', { method: 'POST', body: fd });
if (r.ok) {
showToast('✓ Bild gespeichert');
document.getElementById('gbHeroUploadStatus').textContent = 'Gespeichert!';
} else {
document.getElementById('gbHeroUploadStatus').textContent = 'Fehler beim Upload';
}
} catch(err) {
document.getElementById('gbHeroUploadStatus').textContent = 'Verbindungsfehler';
}
this.disabled = false;
});
async function loadGaestebuch() {
try {
const r = await fetch('/api/gaestebuch');
const d = await r.json();
const hero = d.hero || {};
document.getElementById('gbBadge').value = hero.badge || '';
document.getElementById('gbHeading').value = hero.heading || '';
document.getElementById('gbHeadingColored').value = hero.heading_colored || '';
document.getElementById('gbDescription').value = hero.description || '';
const kontakt = d.kontakt || {};
document.getElementById('gbEmail').value = kontakt.email || '';
document.getElementById('gbDojo').value = kontakt.dojo || '';
if (hero.image) {
document.getElementById('gbHeroPreview').src = '/gaestebuch-img/' + hero.image + '?t=' + Date.now();
document.getElementById('gbHeroPreview').classList.remove('hidden');
document.getElementById('gbHeroPlaceholder').classList.add('hidden');
}
const ef = document.getElementById('gbEintragFields');
ef.innerHTML = '';
(d.eintraege || []).forEach(function(e) {
ef.appendChild(createEintragRow(e.name, e.rolle, e.text, e.farbe, e.breit));
});
} catch(err) { console.error('Gaestebuch load error:', err); }
}
document.getElementById('gbSaveBtn').addEventListener('click', async function() {
try {
const r0 = await fetch('/api/gaestebuch');
const existing = await r0.json();
const rows = document.getElementById('gbEintragFields').querySelectorAll(':scope > div');
const eintraege = Array.from(rows).map(function(row, i) {
return {
id: String(i + 1),
name: row.querySelector('[data-gb="name"]').value,
rolle: row.querySelector('[data-gb="rolle"]').value,
text: row.querySelector('[data-gb="text"]').value,
farbe: row.querySelector('[data-gb="farbe"]').value,
breit: row.querySelector('[data-gb="breit"]').checked
};
});
const data = {
hero: {
badge: document.getElementById('gbBadge').value,
heading: document.getElementById('gbHeading').value,
heading_colored: document.getElementById('gbHeadingColored').value,
description: document.getElementById('gbDescription').value,
image: (existing.hero || {}).image || ''
},
kontakt: {
email: document.getElementById('gbEmail').value,
dojo: document.getElementById('gbDojo').value
},
eintraege: eintraege
};
const res = await fetch('/api/gaestebuch', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (res.ok) showToast('✓ Gästebuch gespeichert & Deploy gestartet');
} catch(err) { console.error('Gaestebuch save error:', err); }
});
// ─── Social Links ─────────────────────────────────────────
async function loadSocialLinks() {
try {
const r = await fetch('/api/global');
const d = await r.json();
const social = d.social || {};
document.getElementById('socialInstagram').value = social.instagram || '';
document.getElementById('socialYoutube').value = social.youtube || '';
document.getElementById('socialEmail').value = social.email || '';
} catch(err) { console.error('Social load error:', err); }
}
document.getElementById('saveSocialBtn').addEventListener('click', async function() {
const status = document.getElementById('socialStatus');
try {
const r = await fetch('/api/global', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
social: {
instagram: document.getElementById('socialInstagram').value,
youtube: document.getElementById('socialYoutube').value,
email: document.getElementById('socialEmail').value
}
})
});
if (r.ok) {
showToast('✓ Social Links gespeichert & Deploy gestartet');
status.textContent = '';
} else {
status.textContent = 'Fehler beim Speichern.';
}
} catch(err) { status.textContent = 'Verbindungsfehler'; }
});
// ─── Init ─────────────────────────────────────────────────
loadCategories().then(function () { loadStats(); });
loadSiteTitle();
loadSocialLinks();
})();
</script>
</body>

51
data/erfolge.json Normal file
View file

@ -0,0 +1,51 @@
{
"hero": {
"badge": "Meine Wettkampf-Geschichte",
"heading_main": "JEDER SIEG",
"heading_colored": "ZÄHLT.",
"description": "Von der ersten Gürtelprüfung bis zur Landesmeisterschaft hier sammle ich alle Momente, auf die ich besonders stolz bin.",
"rang_value": "Blaugurt",
"rang_label": "Aktueller Rang",
"image": "hero.webp"
},
"meilenstein": {
"event": "Landesmeisterschaft Berlin",
"platz": "Gold Kata U14",
"beschreibung": "Mein bisher größter Erfolg. Monatelanges hartes Training gipfelte in einer Vorführung, die meinen Wettkampfgeist unter Beweis stellte.",
"jahr": "2024",
"ort": "Berlin",
"kategorie": "U14"
},
"weitere_auszeichnungen": [
{
"titel": "Norddeutsche Meisterschaft",
"detail": "Silber Kata 2024"
},
{
"titel": "Dojo Champion",
"detail": "Kiai Berlin, intern"
},
{
"titel": "Fairness-Preis",
"detail": "Dojo-Auszeichnung 2023"
}
],
"stats": [
{
"wert": "5+",
"label": "Jahre Training"
},
{
"wert": "32",
"label": "Turniere"
},
{
"wert": "12",
"label": "Goldmedaillen"
}
],
"zitat": {
"text": "Karate ist nicht nur ein Sport. Es ist eine Linse, durch die ich die Welt sehe mit Präzision, Respekt und unbeugsamer Konzentration.",
"autor": "Emy"
}
}

55
data/gaestebuch.json Normal file
View file

@ -0,0 +1,55 @@
{
"hero": {
"badge": "Melde dich bei mir",
"heading": "Schreib mir &",
"heading_colored": "teile deine Geschichte",
"description": "Ob du eine Frage zu meinem Training hast oder einfach eine nette Nachricht hinterlassen möchtest ich freue mich von dir zu hören!",
"image": ""
},
"kontakt": {
"email": "emy@meinewebsite.de",
"dojo": "Kiai Dojo Esslingen"
},
"eintraege": [
{
"id": "1",
"name": "Trainerin Elena",
"rolle": "Sensei",
"text": "Deine Konzentration beim letzten Grading war außergewöhnlich. Die Körperkontrolle, die du entwickelst, ist selten für dein Alter. Ich bin stolz auf dich!",
"farbe": "default",
"breit": false
},
{
"id": "2",
"name": "Opa Karl",
"rolle": "Familie",
"text": "Wir drücken dir von zuhause alle die Daumen! Kann es kaum erwarten, den nächsten Pokal auf dem Regal zu sehen!",
"farbe": "default",
"breit": true
},
{
"id": "3",
"name": "Lea M.",
"rolle": "Freundin",
"text": "Die Website sieht so professionell aus! Mega cool, alle deine Erfolge an einem Ort zu sehen. Wir müssen bald mal wieder eis essen gehen!",
"farbe": "default",
"breit": false
},
{
"id": "4",
"name": "Trainer Thomas",
"rolle": "Dojo Kiai Berlin",
"text": "Ich trainiere viele Kinder, aber die kinetische Flüssigkeit in deinen Bewegungen ist etwas Besonderes. Du machst jede Kata zu einem Tanz. Weiter so der nächste schwarze Gürtel wartet schon!",
"farbe": "secondary",
"breit": true
},
{
"id": "5",
"name": "Herr Braun",
"rolle": "Lehrer",
"text": "Balance im Sport führt zu Balance im Leben. Halt die Disziplin, die du im Dojo zeigst, auch in der Schule!",
"farbe": "default",
"breit": false
}
]
}

17
data/galerie.json Normal file
View file

@ -0,0 +1,17 @@
{
"hero": {
"badge": "Visuelle Reise",
"heading_main": "Eingefangene",
"heading_colored": "Momente.",
"description": "Die Kunst der Disziplin durch die Linse. Von intensiven Trainingseinheiten bis zum Triumph bei Gürtelprüfungen."
},
"ribbon": {
"heading": "Hinter der Linse",
"description": "Unsere Galerie ist nicht nur Fotos sie ist ein Zeugnis der Disziplin, die wir jeden Tag im Dojo leben.",
"stats": [
{ "wert": "24", "label": "Turniere" },
{ "wert": "150+", "label": "Schüler" },
{ "wert": "85", "label": "Gürtelprüfungen" }
]
}
}

7
data/global.json Normal file
View file

@ -0,0 +1,7 @@
{
"social": {
"instagram": "#",
"youtube": "#",
"email": "hallo@miyakarate.de"
}
}

View file

@ -1,21 +1,31 @@
{
"siteTitle": "EmyKarate",
"hero": {
"badge": "MEINE REISE IN KATA hh",
"description": "Entdecke die Welt des Karate durch meine Augen. Von den ersten Schritten im Dojo bis hin zu nationalen Meisterschaften hier teile ich meine Leidenschaft für Bewegung, Disziplin und Erfolg. hh",
"badge": "MEINE REISE IN KATA ",
"description": "Entdecke die Welt des Karate durch meine Augen. Von den ersten Schritten im Dojo bis hin zu nationalen Meisterschaften hier teile ich meine Leidenschaft für Bewegung, Disziplin und Erfolg. ",
"image": "hero.webp"
},
"hero_karte": {
"rang": "Blaugurt",
"status": "Status 2024"
},
"stats": [
{
"value": "7+",
"label": "Jahre Training"
{ "value": "7+", "label": "Jahre Training" },
{ "value": "1", "label": "Gold Medaillen" },
{ "value": "11", "label": "Turniere" }
],
"pruefung_karte": {
"titel": "Prüfung bestanden",
"beschreibung": "Neuer Gürtelgrad erreicht! Jetzt stolze Trägerin des Blaugurts."
},
{
"value": "1",
"label": "Gold Medaillen"
"dojo_karte": {
"titel": "Dojo Champion",
"beschreibung": "Hausinterner Wettbewerb im Dojo \"Kiai Berlin\"."
},
{
"value": "11",
"label": "Turniere"
"cta": {
"heading_main": "Bereit für mehr",
"heading_colored": "Action?",
"description": "Schau dir hunderte Fotos von Trainingseinheiten, Turnieren und Reisen in meiner großen Galerie an.",
"button_text": "ZUR GALERIE GEHEN"
}
]
}

View file

@ -1,9 +1,9 @@
{
"hero": {
"badge": "Meine Reise h",
"heading_line1": "KINETISCHE h",
"heading_line2": "ELEGANZ h",
"heading_suffix": "IN JEDEM SCHLAG. h",
"badge": "Meine Reise ",
"heading_line1": "KINETISCHE",
"heading_line2": "ELEGANZ",
"heading_suffix": "IN JEDEM SCHLAG.",
"description": "Seit ich sechs Jahre alt bin, ist das Dojo mein zweites Zuhause. Für mich ist Karate mehr als Medaillen es ist eine Symphonie aus Fokus, Disziplin und explosiver Energie in einer einzigen Bewegung. hhh",
"image": "portrait.webp",
"image_alt": "Miya beim Karate-Kick",
@ -50,11 +50,11 @@
]
},
"haupterfolg": {
"titel": "Landesmeisterschaft Berlin h",
"platz": "Gold Kata U14 h",
"titel": "Landesmeisterschaft Berlin ",
"platz": "Gold Kata U14 ",
"beschreibung": "Mein bisher größter Erfolg. Monatelanges hartes Training gipfelte in einer Vorführung, die meinen Wettkampfgeist unter Beweis stellte. hh ",
"jahr": "2024 ",
"ort": "Esslingen am Neckar h",
"ort": "Esslingen am Neckar ",
"kategorie": "U14"
},
"weitere_erfolge": [

View file

@ -1,3 +1,3 @@
baseURL = "https://emy.bonzeipunk.de/"
languageCode = "de"
title = "MiyaKarate"
title = "EmyKarate"

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>{{ .Title }} | MiyaKarate</title>
<title>{{ .Title }} | EmyKarate</title>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@700;900&family=Be+Vietnam+Pro:wght@400;500&display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com"></script>
</head>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Erfolge | MiyaKarate</title>
<title>Erfolge | {{ .Site.Data.homepage.siteTitle | default .Site.Title }}</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700;800;900&family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
@ -67,7 +67,7 @@
<!-- TopAppBar -->
<header class="bg-white/70 dark:bg-zinc-900/70 backdrop-blur-lg fixed top-0 left-0 w-full z-50 shadow-xl shadow-pink-500/5">
<div class="flex justify-between items-center h-20 px-6 md:px-12 max-w-screen-2xl mx-auto">
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">MiyaKarate</a>
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</a>
<nav class="hidden md:flex items-center space-x-8 font-headline tracking-tight uppercase font-bold">
<a class="text-zinc-600 dark:text-zinc-400 font-medium hover:text-pink-500 dark:hover:text-pink-300 transition-colors duration-300" href="/">Home</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium hover:text-pink-500 dark:hover:text-pink-300 transition-colors duration-300" href="/galerie/">Galerie</a>
@ -81,24 +81,30 @@
</div>
</header>
{{ $e := .Site.Data.erfolge }}
<main class="pt-32 pb-20">
<!-- Hero -->
<section class="max-w-7xl mx-auto px-6 mb-24">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center">
<div class="lg:col-span-7">
<span class="text-primary font-bold tracking-[0.2em] uppercase text-sm mb-4 block">Meine Wettkampf-Geschichte</span>
<span class="text-primary font-bold tracking-[0.2em] uppercase text-sm mb-4 block">{{ $e.hero.badge }}</span>
<h1 class="text-6xl md:text-8xl font-headline font-extrabold text-on-surface leading-[0.9] tracking-tighter mb-8">
JEDER SIEG <span class="text-glass-gradient">ZÄHLT.</span>
{{ $e.hero.heading_main }} <span class="text-glass-gradient">{{ $e.hero.heading_colored }}</span>
</h1>
<p class="text-xl text-on-surface-variant leading-relaxed max-w-xl">
Von der ersten Gürtelprüfung bis zur Landesmeisterschaft hier sammle ich alle Momente, auf die ich besonders stolz bin.
{{ $e.hero.description }}
</p>
</div>
<div class="lg:col-span-5 relative">
<div class="aspect-[4/5] rounded-xl overflow-hidden editorial-shadow bg-surface-container-highest">
<img class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuD_gAlNnXIZRs_NnR68igfJUfHX1ueNZlrjMt15L5xNz3bnL4235YADrLG-nT3wz2ZWYEZy6Dom8-fgsolN77eu_0JF52Xp-YjWEre5kwxN2D6V5LAoXI_T8I2YSU6LI5ZybF1Y1Ynqp8Y2IzCh0DYZOqY_tlPOuzsExGMn7nO0jWw58sR7Ny1674begJzhSNxELjEE7oQfgLjSZaO17dv1vGG5LykvtL9NEqM2JXdOVh0SlsnUN4416utgLZpV8km6wUza_TY7Fg"
alt="Miya beim Wettkampf"/>
{{ if $e.hero.image }}
<img class="w-full h-full object-cover" src="/erfolge-img/{{ $e.hero.image }}" alt="{{ $e.hero.rang_value }} beim Wettkampf"/>
{{ else }}
<div class="w-full h-full bg-surface-container-high flex items-center justify-center">
<span class="material-symbols-outlined text-surface-variant" style="font-size:6rem;">sports_martial_arts</span>
</div>
{{ end }}
</div>
<div class="absolute -bottom-10 -left-10 bg-white p-8 rounded-lg editorial-shadow hidden md:block">
<div class="flex items-center gap-4">
@ -106,8 +112,8 @@
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">workspace_premium</span>
</div>
<div>
<div class="text-sm font-bold uppercase tracking-widest text-secondary">Aktueller Rang</div>
<div class="text-2xl font-headline font-black text-on-surface">Blaugurt</div>
<div class="text-sm font-bold uppercase tracking-widest text-secondary">{{ $e.hero.rang_label }}</div>
<div class="text-2xl font-headline font-black text-on-surface">{{ $e.hero.rang_value }}</div>
</div>
</div>
</div>
@ -119,49 +125,82 @@
<section class="bg-surface-container-low py-24 rounded-t-[5rem]">
<div class="max-w-7xl mx-auto px-6">
<h2 class="text-4xl font-headline font-bold text-on-surface mb-16">Alle Erfolge</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
{{ range (where .Site.RegularPages "Section" "erfolge") }}
<div class="bg-surface-container-lowest rounded-xl p-8 editorial-shadow flex flex-col justify-between relative overflow-hidden group hover:shadow-xl transition-all duration-500">
<div class="relative z-10">
<div class="flex items-center gap-4 mb-4">
<span class="material-symbols-outlined text-3xl text-primary" style="font-variation-settings: 'FILL' 1;">military_tech</span>
<div class="erfolg-card bg-surface-container-lowest rounded-xl editorial-shadow relative overflow-hidden group transition-all duration-500 hover:shadow-xl">
<!-- Immer sichtbar -->
<div class="p-8 relative z-10">
<div class="flex items-center gap-4 mb-5">
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span class="material-symbols-outlined text-2xl text-primary" style="font-variation-settings: 'FILL' 1;">military_tech</span>
</div>
<span class="bg-secondary text-white px-4 py-1 rounded-full text-xs font-bold uppercase tracking-widest">{{ .Params.rang | default "Highlight" }}</span>
</div>
<h3 class="text-2xl font-headline font-bold text-on-surface mb-3">{{ .Title }}</h3>
<p class="text-on-surface-variant mb-6">{{ .Summary }}</p>
<a class="flex items-center gap-2 text-primary font-bold hover:gap-4 transition-all" href="{{ .RelPermalink }}">
Bericht lesen <span class="material-symbols-outlined">arrow_forward</span>
</a>
<p class="text-on-surface-variant leading-relaxed mb-6">{{ .Summary }}</p>
<button onclick="toggleErfolg(this)"
class="flex items-center gap-2 text-primary font-bold text-sm font-headline uppercase tracking-wider hover:gap-3 transition-all">
<span class="btn-label">Mehr lesen</span>
<span class="material-symbols-outlined transition-transform duration-300 text-base">expand_more</span>
</button>
</div>
<span class="material-symbols-outlined absolute -bottom-10 -right-10 text-[12rem] text-surface-container opacity-10 group-hover:opacity-20 transition-opacity">sports_martial_arts</span>
<!-- Aufklappbarer Inhalt -->
<div class="erfolg-body overflow-hidden transition-all duration-500 ease-in-out" style="max-height:0">
<div class="px-8 pb-8 border-t border-surface-container-high">
<div class="pt-6 text-on-surface-variant leading-relaxed space-y-4 text-base">
{{ .Content }}
</div>
</div>
</div>
<span class="material-symbols-outlined absolute -bottom-10 -right-10 text-[12rem] text-surface-container-high opacity-10 group-hover:opacity-20 transition-opacity pointer-events-none">sports_martial_arts</span>
</div>
{{ end }}
</div>
</div>
</section>
<script>
function toggleErfolg(btn) {
var card = btn.closest('.erfolg-card');
var body = card.querySelector('.erfolg-body');
var icon = btn.querySelector('.material-symbols-outlined');
var label = btn.querySelector('.btn-label');
var isOpen = body.style.maxHeight && body.style.maxHeight !== '0px';
if (isOpen) {
body.style.maxHeight = '0';
icon.style.transform = '';
label.textContent = 'Mehr lesen';
} else {
body.style.maxHeight = body.scrollHeight + 'px';
icon.style.transform = 'rotate(180deg)';
label.textContent = 'Weniger anzeigen';
}
}
</script>
<!-- Meilensteine Bento Grid -->
<section class="max-w-7xl mx-auto px-6 mt-24">
<h2 class="text-5xl font-headline font-black mb-16 tracking-tight">MEILENSTEINE DES <span class="text-primary italic">ERFOLGS</span></h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Große Karte -->
<!-- Große Karte: Haupt-Meilenstein -->
<div class="md:col-span-2 bg-surface-container-lowest rounded-xl p-10 editorial-shadow flex flex-col justify-between relative overflow-hidden group">
<div class="relative z-10">
<div class="flex items-center gap-4 mb-6">
<span class="material-symbols-outlined text-4xl text-primary" style="font-variation-settings: 'FILL' 1;">military_tech</span>
<span class="font-headline font-extrabold text-2xl uppercase italic">Landesmeisterschaft Berlin</span>
<span class="font-headline font-extrabold text-2xl uppercase italic">{{ $e.meilenstein.event }}</span>
</div>
<h3 class="text-4xl font-headline font-bold text-on-surface mb-4">Gold Kata U14</h3>
<p class="text-on-surface-variant max-w-md text-lg">Mein bisher größter Erfolg. Monatelanges hartes Training gipfelte in einer Vorführung, die meinen Wettkampfgeist unter Beweis stellte.</p>
<h3 class="text-4xl font-headline font-bold text-on-surface mb-4">{{ $e.meilenstein.platz }}</h3>
<p class="text-on-surface-variant max-w-md text-lg">{{ $e.meilenstein.beschreibung }}</p>
</div>
<div class="mt-12 flex items-center gap-6 relative z-10">
<div>
<div class="text-3xl font-black text-on-surface">2024</div>
<div class="text-xs uppercase tracking-widest font-bold text-primary">Berlin</div>
<div class="text-3xl font-black text-on-surface">{{ $e.meilenstein.jahr }}</div>
<div class="text-xs uppercase tracking-widest font-bold text-primary">{{ $e.meilenstein.ort }}</div>
</div>
<div class="h-10 w-[1px] bg-outline-variant/30"></div>
<div>
<div class="text-3xl font-black text-on-surface">U14</div>
<div class="text-3xl font-black text-on-surface">{{ $e.meilenstein.kategorie }}</div>
<div class="text-xs uppercase tracking-widest font-bold text-primary">Kategorie</div>
</div>
</div>
@ -171,48 +210,41 @@
<!-- Weitere Auszeichnungen -->
<div class="bg-primary rounded-xl p-8 text-on-primary flex flex-col gap-8 shadow-2xl shadow-primary/20">
<h4 class="font-headline font-bold text-xl border-b border-on-primary/20 pb-4">Weitere Auszeichnungen</h4>
{{ $icons := slice "star" "emoji_events" "rewarded_ads" "workspace_premium" "military_tech" "sports_martial_arts" }}
{{ range $i, $a := $e.weitere_auszeichnungen }}
<div class="flex gap-4">
<div class="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center shrink-0">
<span class="material-symbols-outlined text-xl">star</span>
<span class="material-symbols-outlined text-xl">{{ index $icons (mod $i (len $icons)) }}</span>
</div>
<div>
<p class="font-bold">Norddeutsche Meisterschaft</p>
<p class="text-sm opacity-80">Silber Kata 2024</p>
</div>
</div>
<div class="flex gap-4">
<div class="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center shrink-0">
<span class="material-symbols-outlined text-xl">emoji_events</span>
</div>
<div>
<p class="font-bold">Dojo Champion</p>
<p class="text-sm opacity-80">Kiai Berlin, intern</p>
</div>
</div>
<div class="flex gap-4">
<div class="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center shrink-0">
<span class="material-symbols-outlined text-xl">rewarded_ads</span>
</div>
<div>
<p class="font-bold">Fairness-Preis</p>
<p class="text-sm opacity-80">Dojo-Auszeichnung 2023</p>
<p class="font-bold">{{ $a.titel }}</p>
<p class="text-sm opacity-80">{{ $a.detail }}</p>
</div>
</div>
{{ end }}
</div>
<!-- Stats -->
{{ $stats := $e.stats }}
{{ with index $stats 0 }}
<div class="bg-secondary-container rounded-xl p-8 flex flex-col justify-center gap-2">
<span class="text-on-secondary-container font-headline font-black text-6xl italic">5+</span>
<span class="text-secondary font-bold uppercase tracking-[0.2em] text-sm">Jahre Training</span>
<span class="text-on-secondary-container font-headline font-black text-6xl italic">{{ .wert }}</span>
<span class="text-secondary font-bold uppercase tracking-[0.2em] text-sm">{{ .label }}</span>
</div>
{{ end }}
{{ with index $stats 1 }}
<div class="bg-surface-container-highest rounded-xl p-8 flex flex-col justify-center gap-2">
<span class="text-on-surface font-headline font-black text-6xl italic">32</span>
<span class="text-on-surface-variant font-bold uppercase tracking-[0.2em] text-sm">Turniere</span>
<span class="text-on-surface font-headline font-black text-6xl italic">{{ .wert }}</span>
<span class="text-on-surface-variant font-bold uppercase tracking-[0.2em] text-sm">{{ .label }}</span>
</div>
{{ end }}
{{ with index $stats 2 }}
<div class="bg-white rounded-xl p-8 editorial-shadow flex flex-col justify-center gap-2 border-t-4 border-pink-500">
<span class="text-pink-600 font-headline font-black text-6xl italic">12</span>
<span class="text-zinc-500 font-bold uppercase tracking-[0.2em] text-sm">Goldmedaillen</span>
<span class="text-pink-600 font-headline font-black text-6xl italic">{{ .wert }}</span>
<span class="text-zinc-500 font-bold uppercase tracking-[0.2em] text-sm">{{ .label }}</span>
</div>
{{ end }}
</div>
</section>
@ -220,22 +252,22 @@
<section class="max-w-4xl mx-auto px-6 py-32 text-center">
<span class="material-symbols-outlined text-primary text-6xl mb-8" style="font-variation-settings: 'FILL' 1;">format_quote</span>
<blockquote class="text-3xl md:text-5xl font-headline font-bold text-on-surface leading-tight tracking-tight italic">
"Karate ist nicht nur ein Sport. Es ist eine Linse, durch die ich die Welt sehe mit Präzision, Respekt und unbeugsamer Konzentration."
"{{ $e.zitat.text }}"
</blockquote>
<p class="mt-8 font-bold uppercase tracking-widest text-on-surface-variant">Miya</p>
<p class="mt-8 font-bold uppercase tracking-widest text-on-surface-variant">{{ $e.zitat.autor }}</p>
</section>
</main>
<!-- Footer -->
<footer class="bg-zinc-50 dark:bg-zinc-950 w-full rounded-t-[3rem] mt-20">
<div class="flex flex-col md:flex-row justify-between items-center py-12 px-8 max-w-7xl mx-auto gap-6 text-sm tracking-wide">
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">MiyaKarate</div>
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</div>
<div class="flex gap-8 text-zinc-500">
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">Instagram</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">YouTube</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">Email</a>
</div>
<div class="text-zinc-500 opacity-80">© 2024 MiyaKarate. Alle Rechte vorbehalten.</div>
<div class="text-zinc-500 opacity-80">© 2024 {{ .Site.Data.homepage.siteTitle | default .Site.Title }}. Alle Rechte vorbehalten.</div>
</div>
</footer>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Gästebuch | MiyaKarate</title>
<title>Gästebuch | {{ .Site.Data.homepage.siteTitle | default .Site.Title }}</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@400;700;800;900&family=Be+Vietnam+Pro:wght@400;500;600;700&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
@ -57,16 +57,16 @@
</style>
</head>
<body class="bg-surface text-on-surface font-body selection:bg-primary-container selection:text-on-primary-container">
{{ $gb := .Site.Data.gaestebuch }}
<!-- TopAppBar -->
<header class="fixed top-0 left-0 w-full z-50 bg-white/70 dark:bg-zinc-900/70 backdrop-blur-lg shadow-xl shadow-pink-500/5">
<nav class="flex justify-between items-center h-20 px-6 md:px-12 max-w-screen-2xl mx-auto">
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">MiyaKarate</a>
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</a>
<div class="hidden md:flex items-center gap-8">
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="/">Home</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="/galerie/">Galerie</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="/uebermich/">Über mich</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="#">Erfolge</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="/erfolge/">Erfolge</a>
<a class="text-pink-600 dark:text-pink-400 border-b-2 border-pink-500 pb-1 font-headline tracking-tight uppercase font-bold" href="/gaestebuch/">Gästebuch</a>
</div>
<button class="bg-gradient-to-br from-primary to-primary-container text-on-primary px-8 py-3 rounded-xl font-bold uppercase tracking-wider scale-95 active:scale-90 transition-transform shadow-lg shadow-primary/20">
@ -79,16 +79,20 @@
<!-- Hero -->
<section class="max-w-7xl mx-auto px-6 mb-20">
<div class="relative rounded-xl overflow-hidden min-h-[400px] flex items-center p-8 md:p-16">
{{ if $gb.hero.image }}
<img alt="Gästebuch Hero" class="absolute inset-0 w-full h-full object-cover" src="/gaestebuch-img/{{ $gb.hero.image }}"/>
{{ else }}
<img alt="Karate Dojo" class="absolute inset-0 w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuDlVHSCQA9wTqwkVgz4Q76piwsWS33jFlIXlTw7TBSDubv3dQTnKkIUtU-rBAoPyO3eJ1BHdeKBNqS_CCcyhfI0XH1VHSDFSgxgH1oDxrICcX8b-GoehyY1x8udqwWfuKtSTI-nNW4i15l0HWDcVvSrtlM4u25KjM63nBbo_pfDq6f-larFS9uVzVlBn5KMcj8uioIHmQqPlVxWqlkETOL_6zr_Y-k_doAHs6brW-DdT4clT2MxJjtDFjxxr-cTzgKALEfMvTTJUg"/>
{{ end }}
<div class="absolute inset-0 bg-gradient-to-r from-on-surface/80 via-on-surface/40 to-transparent"></div>
<div class="relative z-10 max-w-2xl">
<span class="inline-block px-4 py-1 rounded-full bg-secondary text-on-secondary font-label text-xs tracking-[0.1em] uppercase mb-4">Melde dich bei mir</span>
<span class="inline-block px-4 py-1 rounded-full bg-secondary text-on-secondary font-label text-xs tracking-[0.1em] uppercase mb-4">{{ $gb.hero.badge }}</span>
<h1 class="font-headline text-5xl md:text-7xl font-extrabold text-white leading-tight mb-6 tracking-tighter">
Schreib mir & <span class="text-primary-container">teile deine Geschichte</span>
{{ $gb.hero.heading }} <span class="text-primary-container">{{ $gb.hero.heading_colored }}</span>
</h1>
<p class="text-lg text-surface font-medium max-w-lg editorial-text">
Ob du eine Frage zu meinem Training hast oder einfach eine nette Nachricht hinterlassen möchtest ich freue mich von dir zu hören!
{{ $gb.hero.description }}
</p>
</div>
</div>
@ -154,7 +158,7 @@
</div>
<div>
<p class="text-xs font-bold uppercase opacity-60">E-Mail</p>
<p class="font-bold">hallo@miyakarate.de</p>
<p class="font-bold">{{ $gb.kontakt.email }}</p>
</div>
</div>
<div class="flex items-center gap-4">
@ -163,7 +167,7 @@
</div>
<div>
<p class="text-xs font-bold uppercase opacity-60">Dojo</p>
<p class="font-bold">Kiai Dojo Berlin</p>
<p class="font-bold">{{ $gb.kontakt.dojo }}</p>
</div>
</div>
</div>
@ -175,23 +179,18 @@
<h3 class="font-headline text-2xl font-bold">Neuester Eintrag</h3>
<a class="text-primary font-bold text-sm hover:underline" href="#gaestebuch-wall">Alle ansehen</a>
</div>
{{ with index $gb.eintraege 0 }}
<div class="glass-card p-6 rounded-lg shadow-sm border border-white/40">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-surface-container-highest overflow-hidden">
<img alt="Gast Avatar" class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuCyr8FcSn01QbwNHFaE4oEJCeSKkIcv6Qjd7WO_jrfclu0LeV9KMmCJ6VqttBmEZM9Cz7ddfK6gmQ8qGQmJ9YWno683_KcaFT_BhaOws3_rpcvPU2j1xquZD0bzluyw84dqcvLLIXsyqWVYMGqlAwXoW78olS5BfpuvMiBkh3bIIgo8kIhWJkq7R1MZB4yCmnTN2UyzrVSy9aUM_vmaU1AZFhWUgk7UOsN1BST2xOACH2NhCB6HP_93OnTDjAAXBja3bufXiAjZkw"/>
</div>
<div class="w-12 h-12 rounded-full bg-secondary text-on-secondary flex items-center justify-center font-bold text-xl">{{ substr .name 0 1 }}</div>
<div>
<h4 class="font-bold">Jonas L.</h4>
<p class="text-xs text-on-surface-variant font-medium">vor 2 Stunden</p>
<h4 class="font-bold">{{ .name }}</h4>
<p class="text-xs text-on-surface-variant font-medium">{{ .rolle }}</p>
</div>
</div>
<p class="text-on-surface-variant leading-relaxed">"Mega Fortschritte bei deiner letzten Kata! Deine Hingabe ist wirklich inspirierend. Weiter so! Oss!"</p>
<div class="mt-4 flex gap-2">
<span class="px-3 py-1 rounded-full bg-primary/10 text-primary text-[10px] font-bold uppercase">Teamkamerad</span>
<span class="px-3 py-1 rounded-full bg-secondary/10 text-secondary text-[10px] font-bold uppercase">Inspiriert</span>
</div>
<p class="text-on-surface-variant leading-relaxed">"{{ .text }}"</p>
</div>
{{ end }}
</div>
</div>
</section>
@ -203,81 +202,33 @@
<p class="text-on-surface-variant max-w-2xl mx-auto">Nachrichten von Trainingspartnern, Familie und Freunden aus aller Welt.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="bg-white rounded-lg p-6 shadow-xl shadow-black/5 hover:-translate-y-2 transition-transform duration-300">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-surface-container overflow-hidden">
<img alt="Gast" class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuAxxSG4TUDr606KVR8_FBXcG1vZu-IryVXVsyk0ildPn-mTI7Xhf3_kclVMF91JkR_jx9eWesri4dDj4vf9U7gKuzWCMe6W52Bwgndg_mOwNf6WgregdNT9uH1bwxH2reR5LR_M8Stb6dLfl3O1u0qC3l8W_jcpH2FrTQY-onMUIiasTQNndMFCSqJy2Y8NBCjJkq7eSFELqR7CicXvpe3dSLeE_WyzxADxwlGyY3tPS7nYZuYCJvctYZl0hDvGF1HYDhKj3Xw5rg"/>
</div>
{{ range $gb.eintraege }}
{{ $span := "" }}{{ if .breit }}{{ $span = "md:col-span-2" }}{{ end }}
{{ if eq .farbe "secondary" }}
<div class="bg-gradient-to-br from-secondary/5 to-primary/5 border border-white rounded-lg p-6 shadow-xl shadow-black/5 {{ $span }}">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-secondary text-on-secondary flex items-center justify-center font-bold text-xl">{{ substr .name 0 1 }}</div>
<div>
<p class="font-bold text-sm">Trainerin Elena</p>
<p class="text-[10px] uppercase font-bold text-outline">Sensei</p>
<p class="font-bold">{{ .name }}</p>
<p class="text-xs font-medium text-secondary">{{ .rolle }}</p>
</div>
</div>
<blockquote class="text-on-surface-variant italic text-lg leading-relaxed">"{{ .text }}"</blockquote>
</div>
{{ else }}
<div class="bg-white rounded-lg p-6 shadow-xl shadow-black/5 hover:-translate-y-2 transition-transform duration-300 {{ $span }}">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-surface-container flex items-center justify-center font-bold text-sm text-on-surface-variant">{{ substr .name 0 1 }}</div>
<div>
<p class="font-bold text-sm">{{ .name }}</p>
<p class="text-[10px] uppercase font-bold text-outline">{{ .rolle }}</p>
</div>
<span class="ml-auto material-symbols-outlined text-primary text-xl" style="font-variation-settings: 'FILL' 1;">favorite</span>
</div>
<p class="text-sm text-on-surface-variant leading-relaxed">Deine Konzentration beim letzten Grading war außergewöhnlich. Die Körperkontrolle, die du entwickelst, ist selten für dein Alter. Ich bin stolz auf dich!</p>
<p class="text-sm text-on-surface-variant leading-relaxed">{{ .text }}</p>
</div>
<div class="bg-white rounded-lg p-6 shadow-xl shadow-black/5 hover:-translate-y-2 transition-transform duration-300">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-surface-container overflow-hidden">
<img alt="Gast" class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuCcEhcufxMA4pz_Ukcsfi9JIzXT6dS8M_1HuhSOhs00ftkIx_Dm74awVwHZURNxLYx0JSSosudUn5s21X0gOha6KwIsVz-esI6HO9WqQcgRm3Y6rxuM0eL2QbJtx16wJdxq9y05JLIjBpLGEQB7iemBIX7lH6RF86cxV0Cq0I_KA_vTkcLwxPunt79vO6NUt-zb0v3PP59gVEHOJOfFnnOlRqTe42kS28CfXGeWY5920CQDOjHFpQ6SH45XSQPSKMICsP5ANa3Wdw"/>
</div>
<div>
<p class="font-bold text-sm">Opa Karl</p>
<p class="text-[10px] uppercase font-bold text-outline">Familie</p>
</div>
</div>
<p class="text-sm text-on-surface-variant leading-relaxed">Wir drücken dir von zuhause alle die Daumen! Kann es kaum erwarten, den nächsten Pokal auf dem Regal zu sehen!</p>
</div>
<div class="bg-white rounded-lg p-6 shadow-xl shadow-black/5 hover:-translate-y-2 transition-transform duration-300">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-surface-container overflow-hidden">
<img alt="Gast" class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuCx78Ehwgx2xoiA9H_WHtwOteg8d8slllmcu2c66Ty-RnDNmX6sUEtNNsUXBXtINwZ4tg8WGxUjfbOSbtBWbcAUlaKqTTGgVOicODmvzt6PsT-hNu-JvwoOsmr-MSza1orgDZlUWKAcCY1XSByhyrJ6pFRT964VljwFvJd9KUKJFtd18GKw5qqh5IqcTt9_jrZFtOMFAGio1uK1GT3pyJA3WyGYMyUAbUWucPVTcnB9qo44WUkfg4k4LcmL7soUZK4bvTGxLg_m-g"/>
</div>
<div>
<p class="font-bold text-sm">Lea M.</p>
<p class="text-[10px] uppercase font-bold text-outline">Freundin</p>
</div>
</div>
<p class="text-sm text-on-surface-variant leading-relaxed">Die Website sieht so professionell aus! Mega cool, alle deine Erfolge an einem Ort zu sehen. Wir müssen bald mal wieder eis essen gehen!</p>
</div>
<div class="bg-gradient-to-br from-secondary/5 to-primary/5 border border-white rounded-lg p-6 shadow-xl shadow-black/5 md:col-span-2">
<div class="flex items-center gap-4 mb-4">
<div class="w-12 h-12 rounded-full bg-secondary text-on-secondary flex items-center justify-center font-bold text-xl">T</div>
<div>
<p class="font-bold">Trainer Thomas</p>
<p class="text-xs font-medium text-secondary">Dojo Kiai Berlin</p>
</div>
</div>
<blockquote class="text-on-surface-variant italic text-lg leading-relaxed mb-4">
"Ich trainiere viele Kinder, aber die kinetische Flüssigkeit in deinen Bewegungen ist etwas Besonderes. Du machst jede Kata zu einem Tanz. Weiter so der nächste schwarze Gürtel wartet schon!"
</blockquote>
</div>
<div class="bg-white rounded-lg p-6 shadow-xl shadow-black/5 hover:-translate-y-2 transition-transform duration-300">
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-surface-container overflow-hidden">
<img alt="Gast" class="w-full h-full object-cover"
src="https://lh3.googleusercontent.com/aida-public/AB6AXuA09PdHK61xpLhrsPtonFk8dj5CKIW26fygdA6IlzV7oS96jGwscnrGsFu947MCZ2oPdbWP8STBciRmN8FDLKPOdHJKd2dBi3-pwlmDsyDAU8Myi4GtGEQLay6v7P_DOoRN64fPfZcEaXRNrje0eYe-DyAiDAXCRfqYVwK_ducZzu4e-ZOQIyXaJRA1LtJwkiF1EHDj2Kcp4z9TmQGWpc9HtLA7D7uQ7XcncDH-HSYLS8ZR3rC4h90gf9_CJfzUfHC8MSeSdQKABg"/>
</div>
<div>
<p class="font-bold text-sm">Herr Braun</p>
<p class="text-[10px] uppercase font-bold text-outline">Lehrer</p>
</div>
</div>
<p class="text-sm text-on-surface-variant leading-relaxed">Balance im Sport führt zu Balance im Leben. Halt die Disziplin, die du im Dojo zeigst, auch in der Schule!</p>
</div>
</div>
<div class="mt-12 text-center">
<button class="bg-surface-container-highest hover:bg-surface-container-high px-10 py-4 rounded-xl font-bold transition-colors">
Weitere Einträge laden
</button>
{{ end }}
{{ end }}
</div>
</section>
</main>
@ -285,13 +236,14 @@
<!-- Footer -->
<footer class="w-full rounded-t-[3rem] mt-20 bg-zinc-50 dark:bg-zinc-950">
<div class="flex flex-col md:flex-row justify-between items-center py-12 px-8 max-w-7xl mx-auto gap-6 text-sm tracking-wide">
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">MiyaKarate</div>
{{ $social := .Site.Data.global.social }}
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</div>
<div class="flex gap-8 text-zinc-500">
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="#">Instagram</a>
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="#">YouTube</a>
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="#">Email</a>
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="{{ $social.instagram }}">Instagram</a>
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="{{ $social.youtube }}">YouTube</a>
<a class="hover:text-pink-500 transition-opacity hover:underline decoration-pink-500 decoration-2 underline-offset-4" href="mailto:{{ $social.email }}">Email</a>
</div>
<div class="text-zinc-500">© 2024 MiyaKarate. Alle Rechte vorbehalten.</div>
<div class="text-zinc-500">© 2024 {{ .Site.Data.homepage.siteTitle | default .Site.Title }}. Alle Rechte vorbehalten.</div>
</div>
</footer>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Galerie | MiyaKarate</title>
<title>Galerie | {{ .Site.Data.homepage.siteTitle | default .Site.Title }}</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;600;700;800;900&family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
@ -61,7 +61,7 @@
<!-- TopAppBar -->
<header class="fixed top-0 left-0 w-full z-50 bg-white/70 dark:bg-zinc-900/70 backdrop-blur-lg shadow-xl shadow-pink-500/5">
<div class="flex justify-between items-center h-20 px-6 md:px-12 max-w-screen-2xl mx-auto">
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">MiyaKarate</a>
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</a>
<nav class="hidden md:flex items-center gap-8">
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 transition-colors duration-300" href="/">Home</a>
<a class="text-pink-600 dark:text-pink-400 border-b-2 border-pink-500 pb-1 font-headline tracking-tight uppercase font-bold" href="/galerie/">Galerie</a>
@ -78,12 +78,13 @@
<main class="pt-32 pb-20 px-6 max-w-7xl mx-auto">
<!-- Header -->
<header class="mb-16 relative">
<span class="font-label text-sm uppercase tracking-[0.2em] font-bold text-primary mb-4 block">Visuelle Reise</span>
{{ $gal := .Site.Data.galerie }}
<span class="font-label text-sm uppercase tracking-[0.2em] font-bold text-primary mb-4 block">{{ $gal.hero.badge }}</span>
<h1 class="font-headline text-5xl md:text-7xl font-black text-on-surface leading-[1.1] tracking-tight mb-6">
Eingefangene <br/><span class="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary">Momente.</span>
{{ $gal.hero.heading_main }} <br/><span class="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary">{{ $gal.hero.heading_colored }}</span>
</h1>
<p class="max-w-xl text-on-surface-variant text-lg leading-relaxed">
Die Kunst der Disziplin durch die Linse. Von intensiven Trainingseinheiten bis zum Triumph bei Gürtelprüfungen.
{{ $gal.hero.description }}
</p>
</header>
@ -159,26 +160,21 @@
<!-- Stats Ribbon -->
<section class="bg-surface-container-low py-20 px-6">
{{ $ribbon := $gal.ribbon }}
{{ $statColors := slice "text-primary" "text-secondary" "text-primary" }}
<div class="max-w-7xl mx-auto flex flex-col md:flex-row justify-between items-center gap-12">
<div class="flex-1">
<h2 class="font-headline text-3xl font-bold mb-4">Hinter der Linse</h2>
<p class="text-on-surface-variant max-w-md">Unsere Galerie ist nicht nur Fotos sie ist ein Zeugnis der Disziplin, die wir jeden Tag im MiyaKarate Dojo leben.</p>
<h2 class="font-headline text-3xl font-bold mb-4">{{ $ribbon.heading }}</h2>
<p class="text-on-surface-variant max-w-md">{{ $ribbon.description }}</p>
</div>
<div class="flex gap-8 overflow-x-auto pb-4 no-scrollbar w-full md:w-auto">
{{ range $i, $s := $ribbon.stats }}
{{ if gt $i 0 }}<div class="h-12 w-px bg-outline-variant/30 hidden md:block"></div>{{ end }}
<div class="flex-shrink-0 text-center">
<p class="text-4xl font-black text-primary font-headline">24</p>
<p class="text-xs uppercase tracking-widest font-bold opacity-60">Turniere</p>
</div>
<div class="h-12 w-px bg-outline-variant/30 hidden md:block"></div>
<div class="flex-shrink-0 text-center">
<p class="text-4xl font-black text-secondary font-headline">150+</p>
<p class="text-xs uppercase tracking-widest font-bold opacity-60">Schüler</p>
</div>
<div class="h-12 w-px bg-outline-variant/30 hidden md:block"></div>
<div class="flex-shrink-0 text-center">
<p class="text-4xl font-black text-primary font-headline">85</p>
<p class="text-xs uppercase tracking-widest font-bold opacity-60">Gürtelprüfungen</p>
<p class="text-4xl font-black {{ index $statColors $i }} font-headline">{{ $s.wert }}</p>
<p class="text-xs uppercase tracking-widest font-bold opacity-60">{{ $s.label }}</p>
</div>
{{ end }}
</div>
</div>
</section>
@ -186,13 +182,14 @@
<!-- Footer -->
<footer class="bg-zinc-50 dark:bg-zinc-950 w-full rounded-t-[3rem] mt-20">
<div class="flex flex-col md:flex-row justify-between items-center py-12 px-8 max-w-7xl mx-auto gap-6 text-sm tracking-wide">
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">MiyaKarate</div>
{{ $social := .Site.Data.global.social }}
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</div>
<div class="flex gap-8">
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">Instagram</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">YouTube</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">Email</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="{{ $social.instagram }}">Instagram</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="{{ $social.youtube }}">YouTube</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="mailto:{{ $social.email }}">Email</a>
</div>
<div class="text-zinc-500">© 2024 MiyaKarate. Alle Rechte vorbehalten.</div>
<div class="text-zinc-500">© 2024 {{ .Site.Data.homepage.siteTitle | default .Site.Title }}. Alle Rechte vorbehalten.</div>
</div>
</footer>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>{{ .Title }} | MiyaKarate</title>
<title>{{ .Title }} | EmyKarate</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700;800;900&family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
@ -97,7 +97,7 @@
<nav class="fixed top-0 left-0 w-full z-50 bg-white/70 dark:bg-zinc-900/70 backdrop-blur-lg shadow-xl shadow-pink-500/5">
<div class="flex justify-between items-center h-20 px-6 md:px-12 max-w-screen-2xl mx-auto">
<div class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">
MiyaKarate
{{ .Site.Data.homepage.siteTitle | default .Site.Title }}
</div>
<div class="hidden md:flex items-center gap-8">
<a class="text-pink-600 dark:text-pink-400 border-b-2 border-pink-500 pb-1 font-headline tracking-tight uppercase font-bold transition-colors duration-300" href="/">Home</a>
@ -107,7 +107,7 @@
<a class="text-zinc-600 dark:text-zinc-400 font-medium font-headline tracking-tight uppercase hover:text-pink-500 dark:hover:text-pink-300 transition-colors duration-300" href="/gaestebuch/">Gästebuch</a>
</div>
<div class="flex items-center gap-4">
<button class="bg-gradient-to-br from-primary to-primary-container text-on-primary px-8 py-3 rounded-xl font-bold font-headline tracking-tight uppercase scale-95 active:scale-90 transition-transform">
<button onclick="document.getElementById('kontaktModal').classList.remove('hidden')" class="bg-gradient-to-br from-primary to-primary-container text-on-primary px-8 py-3 rounded-xl font-bold font-headline tracking-tight uppercase scale-95 active:scale-90 transition-transform">
Kontakt
</button>
<button class="md:hidden text-primary">
@ -144,7 +144,7 @@
{{ if .Site.Data.homepage.hero.image }}
<img alt="Miya beim Karate-Tritt" class="w-full h-full object-cover" src="/hero/{{ .Site.Data.homepage.hero.image }}"/>
{{ else }}
<img alt="Miya beim Karate-Tritt" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAHCy-F1uHI1Lq8D0LuZVWXLZA4URfCtFVtXNvimgi_5HnX4ovI2C3Cl_nLp5awhMAL-cwWnK-fL-yQHgwYZ2SWg5JbnH7RkQtVdUqPCb9PcOAFIzdX9haBXQGoCYywwzqNXK4QqQoJ5XxnamSZghNPUK0pOLszlu2jowGPO8VWtQmD7PcJTOGfpOCUxw8tNzeeNTQsCDmPoGD3N8ZjTyTGD6Sk48MrYJrRUgiRvBi9tznnMXIgqMsN8G0v8JA3aQeF5jQlZmJ3kw"/>
<img alt="Emy beim Karate-Tritt" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAHCy-F1uHI1Lq8D0LuZVWXLZA4URfCtFVtXNvimgi_5HnX4ovI2C3Cl_nLp5awhMAL-cwWnK-fL-yQHgwYZ2SWg5JbnH7RkQtVdUqPCb9PcOAFIzdX9haBXQGoCYywwzqNXK4QqQoJ5XxnamSZghNPUK0pOLszlu2jowGPO8VWtQmD7PcJTOGfpOCUxw8tNzeeNTQsCDmPoGD3N8ZjTyTGD6Sk48MrYJrRUgiRvBi9tznnMXIgqMsN8G0v8JA3aQeF5jQlZmJ3kw"/>
{{ end }}
<div class="absolute bottom-8 -left-8 bg-white/40 backdrop-blur-xl p-6 rounded-lg shadow-xl border border-white/20 -rotate-6">
<div class="flex items-center gap-4">
@ -152,8 +152,8 @@
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">stars</span>
</div>
<div>
<p class="font-headline font-bold text-on-surface uppercase leading-none">Blaugurt</p>
<p class="text-xs text-on-surface-variant uppercase tracking-widest mt-1">Status 2024</p>
<p class="font-headline font-bold text-on-surface uppercase leading-none">{{ .Site.Data.homepage.hero_karte.rang }}</p>
<p class="text-xs text-on-surface-variant uppercase tracking-widest mt-1">{{ .Site.Data.homepage.hero_karte.status }}</p>
</div>
</div>
</div>
@ -203,8 +203,8 @@
<div class="md:col-span-4 bg-primary text-on-primary rounded-xl p-8 flex flex-col justify-between hover:scale-[1.02] transition-transform shadow-xl shadow-primary/20">
<span class="material-symbols-outlined text-4xl" style="font-variation-settings: 'FILL' 1;">military_tech</span>
<div>
<h3 class="text-2xl font-black font-headline mb-2">Prüfung bestanden</h3>
<p class="text-on-primary/80">Neuer Gürtelgrad erreicht! Jetzt stolze Trägerin des Blaugurts.</p>
<h3 class="text-2xl font-black font-headline mb-2">{{ .Site.Data.homepage.pruefung_karte.titel }}</h3>
<p class="text-on-primary/80">{{ .Site.Data.homepage.pruefung_karte.beschreibung }}</p>
</div>
</div>
@ -213,8 +213,8 @@
<div class="w-12 h-12 bg-white rounded-lg flex items-center justify-center mb-6 shadow-sm">
<span class="material-symbols-outlined text-secondary">fitness_center</span>
</div>
<h3 class="text-xl font-black font-headline mb-2">Dojo Champion</h3>
<p class="text-sm text-on-surface-variant italic">Hausinterner Wettbewerb im Dojo "Kiai Berlin".</p>
<h3 class="text-xl font-black font-headline mb-2">{{ .Site.Data.homepage.dojo_karte.titel }}</h3>
<p class="text-sm text-on-surface-variant italic">{{ .Site.Data.homepage.dojo_karte.beschreibung }}</p>
</div>
<!-- Bild: Team Spirit -->
@ -235,10 +235,10 @@
<section class="py-24 relative overflow-hidden">
<div class="absolute inset-0 bg-zinc-900 -z-10"></div>
<div class="max-w-7xl mx-auto px-6 text-center text-white">
<h2 class="text-5xl md:text-7xl font-black font-headline italic mb-8">Bereit für mehr <span class="text-primary-fixed">Action?</span></h2>
<p class="text-xl text-zinc-400 max-w-2xl mx-auto mb-12">Schau dir hunderte Fotos von Trainingseinheiten, Turnieren und Reisen in meiner großen Galerie an.</p>
<a class="inline-flex items-center gap-4 bg-primary text-white px-12 py-6 rounded-full font-black font-headline text-xl hover:bg-primary-dim transition-colors group" href="#">
ZUR GALERIE GEHEN
<h2 class="text-5xl md:text-7xl font-black font-headline italic mb-8">{{ .Site.Data.homepage.cta.heading_main }} <span class="text-primary-fixed">{{ .Site.Data.homepage.cta.heading_colored }}</span></h2>
<p class="text-xl text-zinc-400 max-w-2xl mx-auto mb-12">{{ .Site.Data.homepage.cta.description }}</p>
<a class="inline-flex items-center gap-4 bg-primary text-white px-12 py-6 rounded-full font-black font-headline text-xl hover:bg-primary-dim transition-colors group" href="/galerie/">
{{ .Site.Data.homepage.cta.button_text }}
<span class="material-symbols-outlined group-hover:translate-x-2 transition-transform">auto_awesome_motion</span>
</a>
</div>
@ -252,18 +252,116 @@
<footer class="w-full rounded-t-[3rem] mt-20 bg-zinc-50 dark:bg-zinc-950">
<div class="flex flex-col md:flex-row justify-between items-center py-12 px-8 max-w-7xl mx-auto gap-6">
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">
MiyaKarate
{{ .Site.Data.homepage.siteTitle | default .Site.Title }}
</div>
<div class="flex gap-8 text-sm tracking-wide">
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">Instagram</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">YouTube</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="#">Email</a>
{{ $social := .Site.Data.global.social }}
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="{{ $social.instagram }}">Instagram</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="{{ $social.youtube }}">YouTube</a>
<a class="text-zinc-500 hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity opacity-80 hover:opacity-100" href="mailto:{{ $social.email }}">Email</a>
</div>
<div class="text-sm tracking-wide text-zinc-500">
© 2024 MiyaKarate. Alle Rechte vorbehalten.
© 2024 {{ .Site.Data.homepage.siteTitle | default .Site.Title }}. Alle Rechte vorbehalten.
</div>
</div>
</footer>
<!-- Kontakt Modal -->
<div id="kontaktModal" class="hidden fixed inset-0 z-[100] flex items-center justify-center p-4">
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeKontakt()"></div>
<div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg p-8 animate-none">
<!-- Header -->
<div class="flex items-start justify-between mb-8">
<div>
<span class="inline-block px-3 py-1 rounded-full bg-secondary-container text-on-secondary-container text-xs font-bold tracking-widest uppercase mb-3 font-label">Schreib mir</span>
<h2 class="text-3xl font-black font-headline text-on-surface italic">Kontakt</h2>
</div>
<button onclick="closeKontakt()" class="text-on-surface-variant hover:text-on-surface transition-colors mt-1">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<!-- Formular -->
<form id="kontaktForm" class="space-y-4" onsubmit="submitKontakt(event)">
<div>
<label class="block text-sm font-bold text-on-surface-variant mb-1.5 font-label uppercase tracking-wide">Name</label>
<input type="text" name="name" required placeholder="Dein Name"
class="w-full border-2 border-outline-variant rounded-xl px-4 py-3 text-on-surface focus:outline-none focus:border-primary transition-colors font-body"/>
</div>
<div>
<label class="block text-sm font-bold text-on-surface-variant mb-1.5 font-label uppercase tracking-wide">E-Mail</label>
<input type="email" name="email" required placeholder="deine@email.de"
class="w-full border-2 border-outline-variant rounded-xl px-4 py-3 text-on-surface focus:outline-none focus:border-primary transition-colors font-body"/>
</div>
<div>
<label class="block text-sm font-bold text-on-surface-variant mb-1.5 font-label uppercase tracking-wide">Nachricht</label>
<textarea name="message" required rows="4" placeholder="Deine Nachricht..."
class="w-full border-2 border-outline-variant rounded-xl px-4 py-3 text-on-surface focus:outline-none focus:border-primary transition-colors font-body resize-none"></textarea>
</div>
<!-- Status -->
<div id="kontaktStatus" class="hidden text-sm font-bold text-center py-2 rounded-xl"></div>
<button type="submit" id="kontaktBtn"
class="w-full py-4 rounded-xl font-black font-headline text-lg bg-gradient-to-br from-primary to-primary-container text-on-primary shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-transform">
Absenden
</button>
</form>
</div>
</div>
<script>
function closeKontakt() {
document.getElementById('kontaktModal').classList.add('hidden');
document.getElementById('kontaktForm').reset();
document.getElementById('kontaktStatus').classList.add('hidden');
document.getElementById('kontaktBtn').disabled = false;
document.getElementById('kontaktBtn').textContent = 'Absenden';
}
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeKontakt();
});
async function submitKontakt(e) {
e.preventDefault();
const btn = document.getElementById('kontaktBtn');
const status = document.getElementById('kontaktStatus');
const form = document.getElementById('kontaktForm');
btn.disabled = true;
btn.textContent = 'Wird gesendet…';
const data = Object.fromEntries(new FormData(form));
try {
const res = await fetch('https://formsubmit.co/ajax/{{ .Site.Data.global.social.email }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.success === 'true' || json.success === true) {
status.textContent = '✓ Nachricht gesendet! Ich melde mich bald.';
status.className = 'text-sm font-bold text-center py-2 rounded-xl bg-green-50 text-green-700';
status.classList.remove('hidden');
form.reset();
btn.textContent = 'Gesendet!';
} else {
throw new Error();
}
} catch {
status.textContent = '✗ Fehler beim Senden. Bitte versuche es später nochmal.';
status.className = 'text-sm font-bold text-center py-2 rounded-xl bg-red-50 text-red-700';
status.classList.remove('hidden');
btn.disabled = false;
btn.textContent = 'Absenden';
}
}
</script>
</body>
</html>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Über mich | MiyaKarate</title>
<title>Über mich | {{ .Site.Data.homepage.siteTitle | default .Site.Title }}</title>
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700;800;900&family=Be+Vietnam+Pro:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
@ -68,7 +68,7 @@
<!-- TopAppBar -->
<header class="bg-white/70 dark:bg-zinc-900/70 backdrop-blur-lg fixed top-0 left-0 w-full z-50 shadow-xl shadow-pink-500/5">
<div class="flex justify-between items-center h-20 px-6 md:px-12 max-w-screen-2xl mx-auto">
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">MiyaKarate</a>
<a href="/" class="text-2xl font-black text-pink-600 dark:text-pink-400 italic font-headline tracking-tight uppercase">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</a>
<nav class="hidden md:flex items-center space-x-8 font-headline tracking-tight uppercase font-bold">
<a class="text-zinc-600 dark:text-zinc-400 font-medium hover:text-pink-500 dark:hover:text-pink-300 transition-colors duration-300" href="/">Home</a>
<a class="text-zinc-600 dark:text-zinc-400 font-medium hover:text-pink-500 dark:hover:text-pink-300 transition-colors duration-300" href="/galerie/">Galerie</a>
@ -229,13 +229,14 @@
<!-- Footer -->
<footer class="bg-zinc-50 dark:bg-zinc-950 w-full rounded-t-[3rem] mt-20">
<div class="flex flex-col md:flex-row justify-between items-center py-12 px-8 max-w-7xl mx-auto gap-6 text-sm tracking-wide">
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">MiyaKarate</div>
{{ $social := .Site.Data.global.social }}
<div class="text-lg font-bold text-zinc-900 dark:text-zinc-100 font-headline uppercase italic">{{ .Site.Data.homepage.siteTitle | default .Site.Title }}</div>
<div class="flex gap-8 text-zinc-500">
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">Instagram</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">YouTube</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="#">Email</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="{{ $social.instagram }}">Instagram</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="{{ $social.youtube }}">YouTube</a>
<a class="hover:text-pink-500 hover:underline decoration-pink-500 decoration-2 underline-offset-4 transition-opacity" href="mailto:{{ $social.email }}">Email</a>
</div>
<div class="text-zinc-500 opacity-80">© 2024 MiyaKarate. Alle Rechte vorbehalten.</div>
<div class="text-zinc-500 opacity-80">© 2024 {{ .Site.Data.homepage.siteTitle | default .Site.Title }}. Alle Rechte vorbehalten.</div>
</div>
</footer>

View file

@ -1,5 +1,5 @@
{
"name": "miyakarate",
"name": "EmyKarate",
"version": "1.0.0",
"description": "",
"main": "index.js",