meine erfolge

neue erfolge hinzufügen, tausch von bild, angabe von datu und ort
This commit is contained in:
bonzei 2026-04-13 01:24:27 +02:00
parent ad8e5a5b8e
commit eaddeb16be
5 changed files with 535 additions and 82 deletions

View file

@ -10,9 +10,13 @@ const PORT = 3001;
const IMAGES_DIR = path.join(__dirname, 'static/gallery/images');
const HERO_DIR = path.join(__dirname, 'static/hero');
const UEBERMICH_IMG_DIR = path.join(__dirname, 'static/uebermich');
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');
if (!fs.existsSync(UEBERMICH_IMG_DIR)) fs.mkdirSync(UEBERMICH_IMG_DIR, { recursive: true });
if (!fs.existsSync(HERO_DIR)) fs.mkdirSync(HERO_DIR, { recursive: true });
@ -186,6 +190,43 @@ 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));
// Über mich API
function readUebermich() {
return JSON.parse(fs.readFileSync(UEBERMICH_FILE, 'utf8'));
}
function writeUebermich(data) {
fs.writeFileSync(UEBERMICH_FILE, JSON.stringify(data, null, 2));
}
app.get('/api/uebermich', (req, res) => {
res.json(readUebermich());
});
app.put('/api/uebermich', (req, res) => {
const data = req.body;
writeUebermich(data);
res.json({ ok: true });
rebuildAndDeploy();
});
app.post('/api/uebermich/image', upload.single('image'), async (req, res) => {
try {
const filename = 'portrait.webp';
await sharp(req.file.buffer)
.resize(800, 1000, { fit: 'cover', position: 'top' })
.webp({ quality: 88 })
.toFile(path.join(UEBERMICH_IMG_DIR, filename));
const data = readUebermich();
data.hero.image = filename;
writeUebermich(data);
res.json({ ok: true, image: filename });
rebuildAndDeploy();
} catch (e) {
res.status(500).json({ ok: false, error: e.message });
}
});
// Admin-UI
app.get('/', (req, res) => {

View file

@ -135,6 +135,11 @@ tailwind.config = {
<span class="material-symbols-outlined">home</span>
<span>Startseite</span>
</a>
<a data-section="uebermich"
class="nav-link text-zinc-600 hover:bg-zinc-100 rounded-xl mx-2 flex items-center gap-3 px-4 py-3 font-lexend font-medium hover:translate-x-1 duration-300 transition-all cursor-pointer">
<span class="material-symbols-outlined">person</span>
<span>Über mich</span>
</a>
<a data-section="settings"
class="nav-link text-zinc-600 hover:bg-zinc-100 rounded-xl mx-2 flex items-center gap-3 px-4 py-3 font-lexend font-medium hover:translate-x-1 duration-300 transition-all cursor-pointer">
<span class="material-symbols-outlined">tune</span>
@ -384,6 +389,132 @@ tailwind.config = {
</section>
</div>
<!-- ══ SECTION: ÜBER MICH ══ -->
<div id="section-uebermich" class="hidden">
<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">person</span>
Über mich Seite bearbeiten
</h3>
<!-- Bild -->
<div class="mb-8">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-3">Portrait-Bild</label>
<div class="flex gap-6 items-start flex-wrap">
<div class="w-36 h-44 rounded-xl overflow-hidden bg-surface-container flex-shrink-0">
<img id="umPreview" src="" alt="Portrait" class="w-full h-full object-cover hidden"/>
<div id="umPlaceholder" 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="umFileInput" 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="umFileInput" accept="image/*" class="sr-only"/>
<button id="umUploadBtn" 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="umUploadStatus" class="text-xs text-on-surface-variant mt-2 text-center"></p>
</div>
</div>
</div>
<!-- Hero-Texte -->
<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">Badge (z.B. "Meine Reise")</label>
<input id="umBadge" 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</label>
<input id="umRang" 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 Zeile 1</label>
<input id="umH1" 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 Zeile 2 (farbig)</label>
<input id="umH2" 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">Überschrift Rest</label>
<input id="umHSuffix" 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>
<div class="mb-6">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Beschreibungstext</label>
<textarea id="umDesc" rows="4" 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>
<!-- Aktiver Gurt -->
<div class="mb-6">
<label class="block text-xs font-bold text-on-surface-variant uppercase tracking-widest mb-2">Aktiver Gurt</label>
<select id="umGurt" 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">
<option>Weiß</option><option>Gelb</option><option>Orange</option><option>Grün</option>
<option>Blau</option><option>Lila</option><option>Braun</option><option>Schwarz</option>
</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>
<!-- 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="umZitat" 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="umZitatAutor" 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="umSaveBtn" 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 & deployen
</button>
</section>
</div>
<!-- ══ SECTION: CAREER ══ -->
<div id="section-career" class="hidden">
<div class="bg-surface-container-low rounded-xl p-12 text-center">
@ -477,11 +608,12 @@ tailwind.config = {
let pendingFiles = [];
// ─── Navigation ──────────────────────────────────────────
const sections = ['overview', 'media', 'homepage', 'career', 'community', 'settings'];
const sections = ['overview', 'media', 'homepage', 'uebermich', 'career', 'community', 'settings'];
const pageTitles = {
overview: ['Übersicht', 'Willkommen zurück. Hier ist dein aktueller Stand.'],
media: ['Galerie', 'Fotos hochladen und verwalten.'],
homepage: ['Startseite', 'Hero-Bild und Text der Startseite bearbeiten.'],
uebermich: ['Über mich', 'Seite "Über mich" bearbeiten Text, Bild, Erfolge, Zitat.'],
career: ['Erfolge', 'Meilensteine und Gürtelprüfungen.'],
community: ['Gästebuch', 'Kommentare und Einträge.'],
settings: ['Einstellungen', 'Konfiguration des Admin-Bereichs.']
@ -504,6 +636,7 @@ tailwind.config = {
document.getElementById('pageSubtitle').textContent = sub;
if (name === 'media') loadPhotos();
if (name === 'homepage') loadHomepage();
if (name === 'uebermich') loadUebermich();
if (name === 'settings') renderCatManage();
}
@ -1122,6 +1255,167 @@ tailwind.config = {
} catch (err) { console.error('Save error:', err); }
});
// ─── Über mich ────────────────────────────────────────────
function createWeitereRow(titel, detail, beschreibung, jahr, ort, kategorie) {
const row = document.createElement('div');
row.className = 'bg-surface-container-low rounded-xl p-4 flex flex-col gap-2';
row.innerHTML =
'<div class="flex gap-3 items-center">' +
'<input type="text" value="' + (titel||'') + '" placeholder="Titel" data-we="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="' + (detail||'') + '" placeholder="Untertitel (z.B. Silber Kata 2024)" data-we="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="we-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-we="beschreibung" rows="2" placeholder="Beschreibung (optional)"' +
' 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">' + (beschreibung||'') + '</textarea>' +
'<div class="flex gap-3">' +
'<input type="text" value="' + (jahr||'') + '" placeholder="Jahr (z.B. 2024)" data-we="jahr"' +
' class="w-28 bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>' +
'<input type="text" value="' + (ort||'') + '" placeholder="Ort (z.B. Berlin)" data-we="ort"' +
' 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"/>' +
'<input type="text" value="' + (kategorie||'') + '" placeholder="Kategorie (z.B. U14)" data-we="kategorie"' +
' class="w-28 bg-white rounded-DEFAULT px-3 py-2 text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/30 text-sm"/>' +
'</div>';
row.querySelector('.we-delete').addEventListener('click', function() { row.remove(); });
return row;
}
document.getElementById('umWeitereAdd').addEventListener('click', function() {
document.getElementById('umWeitereFields').appendChild(createWeitereRow('', ''));
});
async function loadUebermich() {
try {
const r = await fetch('/api/uebermich');
const d = await r.json();
document.getElementById('umBadge').value = d.hero.badge || '';
document.getElementById('umRang').value = d.hero.rang_value || '';
document.getElementById('umH1').value = d.hero.heading_line1 || '';
document.getElementById('umH2').value = d.hero.heading_line2 || '';
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 = '';
(d.stats || []).forEach(function(s) {
const row = document.createElement('div');
row.className = 'flex gap-3 items-center';
row.innerHTML =
'<input type="text" value="' + (s.wert||'') + '" placeholder="Wert" data-us="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="' + (s.label||'') + '" placeholder="Bezeichnung" data-us="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);
});
if (d.hero.image) {
document.getElementById('umPreview').src = '/uebermich/' + d.hero.image + '?t=' + Date.now();
document.getElementById('umPreview').classList.remove('hidden');
document.getElementById('umPlaceholder').classList.add('hidden');
}
} catch(err) { console.error('Uebermich load error:', err); }
}
document.getElementById('umFileInput').addEventListener('change', function() {
const file = this.files[0];
if (!file) return;
document.getElementById('umPreview').src = URL.createObjectURL(file);
document.getElementById('umPreview').classList.remove('hidden');
document.getElementById('umPlaceholder').classList.add('hidden');
document.getElementById('umUploadBtn').disabled = false;
document.getElementById('umUploadStatus').textContent = file.name;
});
document.getElementById('umUploadBtn').addEventListener('click', async function() {
const file = document.getElementById('umFileInput').files[0];
if (!file) return;
this.disabled = true;
document.getElementById('umUploadStatus').textContent = 'Wird hochgeladen…';
try {
const fd = new FormData();
fd.append('image', file);
const r = await fetch('/api/uebermich/image', { method: 'POST', body: fd });
if (r.ok) {
showToast('✓ Bild gespeichert');
document.getElementById('umUploadStatus').textContent = 'Gespeichert!';
} else {
document.getElementById('umUploadStatus').textContent = 'Fehler beim Upload';
}
} catch(err) {
document.getElementById('umUploadStatus').textContent = 'Verbindungsfehler';
}
this.disabled = false;
});
document.getElementById('umSaveBtn').addEventListener('click', async function() {
try {
const r = await fetch('/api/uebermich');
const data = await r.json();
data.hero.badge = document.getElementById('umBadge').value;
data.hero.rang_value = document.getElementById('umRang').value;
data.hero.heading_line1 = document.getElementById('umH1').value;
data.hero.heading_line2 = document.getElementById('umH2').value;
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 {
wert: row.querySelector('[data-us="wert"]').value,
label: row.querySelector('[data-us="label"]').value
};
});
const res = await fetch('/api/uebermich', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (res.ok) showToast('✓ Über mich gespeichert & Deploy gestartet');
} catch(err) { console.error('Save error:', err); }
});
// ─── Init ─────────────────────────────────────────────────
loadCategories().then(function () { loadStats(); });
})();

View file

@ -1 +1,6 @@
["Training", "Wettkämpfe", "Gürtelprüfungen"]
[
"Training",
"Wettkämpfe",
"Gürtelprüfungen",
"test"
]

112
data/uebermich.json Normal file
View file

@ -0,0 +1,112 @@
{
"hero": {
"badge": "Meine Reise h",
"heading_line1": "KINETISCHE h",
"heading_line2": "ELEGANZ h",
"heading_suffix": "IN JEDEM SCHLAG. h",
"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",
"rang_label": "Aktueller Rang",
"rang_value": "Lila "
},
"gurtweg": {
"title": "Der Weg zur Meisterschaft",
"subtitle": "Jeder Gürtel erzählt eine Geschichte aus Hingabe und Wachstum.",
"aktiver_gurt": "Lila",
"gurte": [
{
"name": "Weiß",
"farbe": "bg-white border-2 border-zinc-300"
},
{
"name": "Gelb",
"farbe": "bg-yellow-400"
},
{
"name": "Orange",
"farbe": "bg-orange-500"
},
{
"name": "Grün",
"farbe": "bg-green-600"
},
{
"name": "Blau",
"farbe": "bg-blue-600"
},
{
"name": "Lila",
"farbe": "bg-purple-700"
},
{
"name": "Braun",
"farbe": "bg-red-700"
},
{
"name": "Schwarz",
"farbe": "bg-zinc-900"
}
]
},
"haupterfolg": {
"titel": "Landesmeisterschaft Berlin h",
"platz": "Gold Kata U14 h",
"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",
"kategorie": "U14"
},
"weitere_erfolge": [
{
"titel": "Norddeutsche Meisterschaft",
"detail": "Silber Kata 2024",
"beschreibung": "fdfffdfdfdfd\nfdf",
"jahr": "2099",
"ort": "Heilbronn",
"kategorie": "haha"
},
{
"titel": "Dojo Champion",
"detail": "Kiai Berlin, intern",
"beschreibung": "fdffdfdfde",
"jahr": "",
"ort": "",
"kategorie": ""
},
{
"titel": "Fairness-Preis",
"detail": "Dojo-Auszeichnung 2023",
"beschreibung": "hghghfghfghfghfg",
"jahr": "",
"ort": "",
"kategorie": ""
},
{
"titel": "Der beste",
"detail": "Ganz oben auf dem Treppchen",
"beschreibung": "eqweqweqw",
"jahr": "",
"ort": "",
"kategorie": ""
}
],
"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"
}
}

View file

@ -62,6 +62,7 @@
.editorial-shadow { box-shadow: 0 24px 40px -10px rgba(44, 47, 48, 0.06); }
</style>
</head>
{{ $um := .Site.Data.uebermich }}
<body class="bg-surface font-body text-on-surface">
<!-- TopAppBar -->
@ -86,19 +87,21 @@
<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 Reise</span>
<span class="text-primary font-bold tracking-[0.2em] uppercase text-sm mb-4 block">{{ $um.hero.badge }}</span>
<h1 class="text-6xl md:text-8xl font-headline font-extrabold text-on-surface leading-[0.9] tracking-tighter mb-8">
KINETISCHE <span class="text-glass-gradient">ELEGANZ</span> IN JEDEM SCHLAG.
{{ $um.hero.heading_line1 }} <span class="text-glass-gradient">{{ $um.hero.heading_line2 }}</span> {{ $um.hero.heading_suffix }}
</h1>
<p class="text-xl text-on-surface-variant leading-relaxed max-w-xl">
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.
{{ $um.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 Karate-Kick"/>
{{ if $um.hero.image }}
<img class="w-full h-full object-cover" src="/uebermich/{{ $um.hero.image }}" alt="{{ $um.hero.image_alt }}"/>
{{ else }}
<div class="w-full h-full flex items-center justify-center text-on-surface-variant text-sm">Kein Bild hochgeladen</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 +109,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">{{ $um.hero.rang_label }}</div>
<div class="text-2xl font-headline font-black text-on-surface">{{ $um.hero.rang_value }}</div>
</div>
</div>
</div>
@ -120,8 +123,8 @@
<div class="max-w-7xl mx-auto px-6">
<div class="flex flex-col md:flex-row justify-between items-end mb-16 gap-6">
<div>
<h2 class="text-4xl font-headline font-bold text-on-surface mb-2">Der Weg zur Meisterschaft</h2>
<p class="text-on-surface-variant">Jeder Gürtel erzählt eine Geschichte aus Hingabe und Wachstum.</p>
<h2 class="text-4xl font-headline font-bold text-on-surface mb-2">{{ $um.gurtweg.title }}</h2>
<p class="text-on-surface-variant">{{ $um.gurtweg.subtitle }}</p>
</div>
<div class="flex gap-2">
<span class="px-4 py-2 bg-secondary text-on-secondary rounded-full text-xs font-bold uppercase tracking-widest">Aktiver Status</span>
@ -129,115 +132,87 @@
</div>
</div>
<div class="flex flex-wrap justify-between gap-4">
<div class="flex flex-col items-center gap-4 opacity-40">
<div class="w-24 h-4 bg-white rounded-full border-2 border-zinc-300"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Weiß</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-40">
<div class="w-24 h-4 bg-yellow-400 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Gelb</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-40">
<div class="w-24 h-4 bg-orange-500 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Orange</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-50">
<div class="w-24 h-4 bg-green-600 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Grün</span>
</div>
<div class="flex flex-col items-center gap-4">
<div class="w-24 h-6 bg-blue-600 rounded-full shadow-lg ring-4 ring-primary/20"></div>
<span class="font-bold text-xs uppercase tracking-tighter text-primary">Blau ✓</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-30">
<div class="w-24 h-4 bg-purple-700 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Lila</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-20">
<div class="w-24 h-4 bg-red-700 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Braun</span>
</div>
<div class="flex flex-col items-center gap-4 opacity-10">
<div class="w-24 h-4 bg-zinc-900 rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter">Schwarz</span>
{{ range $um.gurtweg.gurte }}
{{ $aktiv := eq .name $um.gurtweg.aktiver_gurt }}
<div class="flex flex-col items-center gap-4 {{ if not $aktiv }}{{ if eq .name "Schwarz" }}opacity-10{{ else if eq .name "Braun" }}opacity-20{{ else if eq .name "Lila" }}opacity-30{{ else if eq .name "Grün" }}opacity-50{{ else }}opacity-40{{ end }}{{ end }}">
<div class="w-24 {{ if $aktiv }}h-6 shadow-lg ring-4 ring-primary/20{{ else }}h-4{{ end }} {{ .farbe }} rounded-full"></div>
<span class="font-bold text-xs uppercase tracking-tighter {{ if $aktiv }}text-primary{{ end }}">{{ .name }}{{ if $aktiv }} ✓{{ end }}</span>
</div>
{{ end }}
</div>
</div>
</section>
<!-- Meilensteine Bento Grid -->
<!-- Meilensteine -->
<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 Achievement-Karte -->
<!-- Haupterfolg -->
<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 id="he-titel" class="font-headline font-extrabold text-2xl uppercase italic">{{ $um.haupterfolg.titel }}</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 id="he-platz" class="text-4xl font-headline font-bold text-on-surface mb-4">{{ $um.haupterfolg.platz }}</h3>
<p id="he-desc" class="text-on-surface-variant max-w-md text-lg">{{ $um.haupterfolg.beschreibung }}</p>
</div>
<div class="mt-12 flex items-center gap-6 relative z-10">
<div class="text-left">
<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 id="he-jahr" class="text-3xl font-black text-on-surface">{{ $um.haupterfolg.jahr }}</div>
<div id="he-ort" class="text-xs uppercase tracking-widest font-bold text-primary">{{ $um.haupterfolg.ort }}</div>
</div>
<div class="h-10 w-[1px] bg-outline-variant/30"></div>
<div class="text-left">
<div class="text-3xl font-black text-on-surface">U14</div>
<div id="he-kat" class="text-3xl font-black text-on-surface">{{ $um.haupterfolg.kategorie }}</div>
<div class="text-xs uppercase tracking-widest font-bold text-primary">Kategorie</div>
</div>
</div>
<span class="material-symbols-outlined absolute -bottom-10 -right-10 text-[15rem] text-surface-container opacity-20 group-hover:opacity-30 transition-opacity">sports_martial_arts</span>
</div>
<!-- Weitere Auszeichnungen -->
<!-- Weitere Erfolge -->
<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 Erfolge</h4>
<div class="flex gap-4">
{{ $icons := slice "star" "emoji_events" "rewarded_ads" "military_tech" "workspace_premium" }}
{{ range $i, $e := $um.weitere_erfolge }}
<div class="weitere-erfolg-item flex gap-4 cursor-pointer rounded-lg p-2 -m-2 hover:bg-white/10 transition-all active:scale-95"
data-titel="{{ $e.titel }}"
data-detail="{{ $e.detail }}"
data-beschreibung="{{ $e.beschreibung }}"
data-jahr="{{ $e.jahr }}"
data-ort="{{ $e.ort }}"
data-kategorie="{{ $e.kategorie }}">
<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 $i }}</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">{{ $e.titel }}</p>
<p class="text-sm opacity-80">{{ $e.detail }}</p>
</div>
</div>
{{ end }}
</div>
<!-- Stats -->
{{ range $i, $s := $um.stats }}
{{ if eq $i 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">{{ $s.wert }}</span>
<span class="text-secondary font-bold uppercase tracking-[0.2em] text-sm">{{ $s.label }}</span>
</div>
{{ else if eq $i 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">{{ $s.wert }}</span>
<span class="text-on-surface-variant font-bold uppercase tracking-[0.2em] text-sm">{{ $s.label }}</span>
</div>
{{ else }}
<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">{{ $s.wert }}</span>
<span class="text-zinc-500 font-bold uppercase tracking-[0.2em] text-sm">{{ $s.label }}</span>
</div>
{{ end }}
{{ end }}
</div>
</section>
@ -245,9 +220,9 @@
<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."
"{{ $um.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">{{ $um.zitat.autor }}</p>
</section>
</main>
@ -264,5 +239,31 @@
</div>
</footer>
<script>
document.querySelectorAll('.weitere-erfolg-item').forEach(function(item) {
item.addEventListener('click', function() {
const titel = this.dataset.titel;
const detail = this.dataset.detail;
const beschreibung = this.dataset.beschreibung || '';
const jahr = this.dataset.jahr || '';
const ort = this.dataset.ort || '';
const kategorie = this.dataset.kategorie || '';
// Hauptkarte tauschen
document.getElementById('he-titel').textContent = titel;
document.getElementById('he-platz').textContent = detail;
document.getElementById('he-desc').textContent = beschreibung;
document.getElementById('he-jahr').textContent = jahr;
document.getElementById('he-ort').textContent = ort;
document.getElementById('he-kat').textContent = kategorie;
// Aktives Item hervorheben
document.querySelectorAll('.weitere-erfolg-item').forEach(function(el) {
el.classList.remove('bg-white/20', 'ring-2', 'ring-white/40');
});
this.classList.add('bg-white/20', 'ring-2', 'ring-white/40');
});
});
</script>
</body>
</html>