const express = require('express');
const multer = require('multer');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');

const app = express();
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 });

app.use(express.json());
app.use('/images', express.static(IMAGES_DIR));

// Multer: temporärer Speicher, dann sharp übernimmt
const upload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 20 * 1024 * 1024 },
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) cb(null, true);
    else cb(new Error('Nur Bilder erlaubt!'));
  }
});

function readGallery() {
  return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
}
function writeGallery(data) {
  fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}

// Upload
app.post('/api/upload', upload.array('photos', 20), async (req, res) => {
  try {
    const gallery = readGallery();
    const added = [];

    for (const file of req.files) {
      const id = Date.now() + '-' + Math.random().toString(36).slice(2, 7);
      const filename = id + '.webp';
      const thumbname = id + '-thumb.webp';

      // Vollbild (max 1200px)
      await sharp(file.buffer)
        .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
        .webp({ quality: 85 })
        .toFile(path.join(IMAGES_DIR, filename));

      // Thumbnail (400px)
      await sharp(file.buffer)
        .resize(400, 400, { fit: 'cover' })
        .webp({ quality: 80 })
        .toFile(path.join(IMAGES_DIR, thumbname));

      const photo = {
        id,
        filename,
        thumb: thumbname,
        title: req.body.title || file.originalname.replace(/\.[^.]+$/, ''),
        kategorie: req.body.kategorie || 'Training',
        datum: new Date().toISOString().slice(0, 10)
      };
      gallery.photos.unshift(photo);
      added.push(photo);
    }

    writeGallery(gallery);
    res.json({ ok: true, added });
  } catch (e) {
    res.status(500).json({ ok: false, error: e.message });
  }
});

// Foto-Metadaten updaten
app.put('/api/photo/:id', (req, res) => {
  const gallery = readGallery();
  const photo = gallery.photos.find(p => p.id === req.params.id);
  if (!photo) return res.status(404).json({ ok: false });
  if (req.body.title !== undefined) photo.title = req.body.title;
  if (req.body.kategorie !== undefined) photo.kategorie = req.body.kategorie;
  writeGallery(gallery);
  res.json({ ok: true, photo });
});

// Foto löschen
app.delete('/api/photo/:id', (req, res) => {
  const gallery = readGallery();
  const idx = gallery.photos.findIndex(p => p.id === req.params.id);
  if (idx === -1) return res.status(404).json({ ok: false });
  const [photo] = gallery.photos.splice(idx, 1);
  [photo.filename, photo.thumb].forEach(f => {
    const fp = path.join(IMAGES_DIR, f);
    if (fs.existsSync(fp)) fs.unlinkSync(fp);
  });
  writeGallery(gallery);
  res.json({ ok: true });
});

// Alle Fotos
app.get('/api/photos', (req, res) => {
  res.json(readGallery());
});

// ── Kategorien API ────────────────────────────────────────────
function readCategories() {
  if (!fs.existsSync(CATEGORIES_FILE)) return ['Training', 'Wettkämpfe', 'Gürtelprüfungen'];
  return JSON.parse(fs.readFileSync(CATEGORIES_FILE, 'utf8'));
}
function writeCategories(cats) {
  fs.writeFileSync(CATEGORIES_FILE, JSON.stringify(cats, null, 2));
}

app.get('/api/categories', (req, res) => {
  res.json(readCategories());
});

app.post('/api/categories', (req, res) => {
  const name = (req.body.name || '').trim();
  if (!name) return res.status(400).json({ ok: false, error: 'Name fehlt' });
  const cats = readCategories();
  if (cats.includes(name)) return res.status(409).json({ ok: false, error: 'Existiert bereits' });
  cats.push(name);
  writeCategories(cats);
  res.json({ ok: true, categories: cats });
});

app.delete('/api/categories/:name', (req, res) => {
  const cats = readCategories().filter(c => c !== req.params.name);
  writeCategories(cats);
  res.json({ ok: true, categories: cats });
});

// ── Homepage API ──────────────────────────────────────────────
function readHomepage() {
  return JSON.parse(fs.readFileSync(HOMEPAGE_FILE, 'utf8'));
}
function writeHomepage(data) {
  fs.writeFileSync(HOMEPAGE_FILE, JSON.stringify(data, null, 2));
}

app.get('/api/homepage', (req, res) => {
  res.json(readHomepage());
});

function rebuildAndDeploy() {
  const SSH_KEY = '/Users/jessi/.ssh/vpsserver/vpsserver';
  const cmd = `cd /Users/jessi/karatehp && hugo --minify && SSH_ASKPASS_REQUIRE=never ssh-add ${SSH_KEY} <<< "bonzeikiller" 2>/dev/null; rsync -az --delete -e "ssh -o StrictHostKeyChecking=no -i ${SSH_KEY}" /Users/jessi/karatehp/public/ root@217.160.212.198:/var/www/emy.bonzeipunk.de/`;
  exec(cmd, { shell: '/bin/bash' }, (err) => {
    if (err) console.error('Deploy-Fehler:', err.message);
    else console.log('✓ Deploy erfolgreich');
  });
}

app.put('/api/homepage', (req, res) => {
  const hp = readHomepage();
  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;
  writeHomepage(hp);
  res.json({ ok: true });
  rebuildAndDeploy();
});

app.post('/api/homepage/image', upload.single('image'), async (req, res) => {
  try {
    const filename = 'hero.webp';
    await sharp(req.file.buffer)
      .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
      .webp({ quality: 88 })
      .toFile(path.join(HERO_DIR, filename));
    const hp = readHomepage();
    hp.hero.image = filename;
    writeHomepage(hp);
    res.json({ ok: true, image: filename });
    rebuildAndDeploy();
  } catch (e) {
    res.status(500).json({ ok: false, error: e.message });
  }
});

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) => {
  res.sendFile(path.join(__dirname, 'admin.html'));
});

app.get('/__old', (req, res) => {
  res.send(`<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Galerie verwalten 📸</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
  * { font-family: 'Segoe UI', system-ui, sans-serif; }
  .drop-zone { border: 3px dashed #e879a0; transition: all .2s; }
  .drop-zone.drag-over { background: #fdf2f8; border-color: #b30065; transform: scale(1.01); }
  .photo-card { transition: transform .2s, box-shadow .2s; }
  .photo-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(179,0,101,.15); }
  .kategorie-btn.active { background: #8930b0; color: white; }
  .progress-bar { transition: width .3s; }
</style>
</head>
<body class="bg-pink-50 min-h-screen">

<!-- Header -->
<header class="bg-white shadow-md shadow-pink-100 sticky top-0 z-50">
  <div class="max-w-5xl mx-auto px-6 py-4 flex items-center justify-between">
    <div>
      <h1 class="text-2xl font-black text-pink-600 italic uppercase tracking-tight">MiyaKarate</h1>
      <p class="text-xs text-gray-400 font-medium">Galerie verwalten</p>
    </div>
    <a href="http://localhost:1313/galerie/" target="_blank"
      class="flex items-center gap-2 bg-pink-600 text-white px-5 py-2.5 rounded-full font-bold text-sm hover:bg-pink-700 transition-colors">
      🌐 Website ansehen
    </a>
  </div>
</header>

<main class="max-w-5xl mx-auto px-6 py-10 space-y-10">

  <!-- Upload-Bereich -->
  <section class="bg-white rounded-3xl p-8 shadow-sm">
    <h2 class="text-xl font-black text-gray-800 mb-6 flex items-center gap-2">
      <span class="text-3xl">📤</span> Fotos hochladen
    </h2>

    <!-- Drop-Zone -->
    <div id="dropZone"
      class="drop-zone rounded-2xl p-12 text-center cursor-pointer mb-6"
      onclick="document.getElementById('fileInput').click()">
      <div class="text-6xl mb-4">🖼️</div>
      <p class="text-xl font-bold text-pink-600 mb-2">Fotos hier reinziehen</p>
      <p class="text-gray-400 text-sm">oder hier klicken zum Auswählen</p>
      <p class="text-gray-300 text-xs mt-2">JPG, PNG, HEIC • bis zu 20 MB</p>
    </div>
    <input type="file" id="fileInput" multiple accept="image/*" class="hidden"/>

    <!-- Metadaten -->
    <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">
      <div>
        <label class="block text-sm font-bold text-gray-600 mb-2">📝 Titel (optional)</label>
        <input id="uploadTitle" type="text" placeholder="z.B. Landesmeisterschaft 2024"
          class="w-full border-2 border-pink-100 rounded-xl px-4 py-3 focus:outline-none focus:border-pink-400 text-gray-700"/>
      </div>
      <div>
        <label class="block text-sm font-bold text-gray-600 mb-2">🏷️ Kategorie</label>
        <div class="flex gap-2 flex-wrap pt-1">
          <button onclick="setKat(this,'Training')" class="kategorie-btn active px-4 py-2 rounded-full border-2 border-purple-200 text-sm font-bold transition-colors">Training</button>
          <button onclick="setKat(this,'Wettkämpfe')" class="kategorie-btn px-4 py-2 rounded-full border-2 border-purple-200 text-sm font-bold transition-colors bg-white text-gray-600">Wettkämpfe</button>
          <button onclick="setKat(this,'Gürtelprüfungen')" class="kategorie-btn px-4 py-2 rounded-full border-2 border-purple-200 text-sm font-bold transition-colors bg-white text-gray-600">Gürtelprüfungen</button>
        </div>
      </div>
    </div>

    <!-- Vorschau der gewählten Dateien -->
    <div id="previewList" class="grid grid-cols-3 sm:grid-cols-5 gap-3 mb-6 hidden"></div>

    <!-- Fortschrittsbalken -->
    <div id="progressWrap" class="hidden mb-4">
      <div class="h-3 bg-pink-100 rounded-full overflow-hidden">
        <div id="progressBar" class="progress-bar h-full bg-gradient-to-r from-pink-500 to-purple-500 rounded-full" style="width:0%"></div>
      </div>
      <p id="progressText" class="text-sm text-gray-500 mt-2 text-center">Wird hochgeladen…</p>
    </div>

    <button id="uploadBtn" onclick="startUpload()" disabled
      class="w-full py-4 rounded-2xl font-black text-xl bg-gradient-to-r from-pink-500 to-purple-600 text-white shadow-lg shadow-pink-200 disabled:opacity-40 disabled:cursor-not-allowed hover:scale-[1.01] active:scale-95 transition-transform">
      ✨ Hochladen!
    </button>
  </section>

  <!-- Meine Fotos -->
  <section class="bg-white rounded-3xl p-8 shadow-sm">
    <div class="flex items-center justify-between mb-6">
      <h2 class="text-xl font-black text-gray-800 flex items-center gap-2">
        <span class="text-3xl">🗂️</span> Meine Fotos
        <span id="photoCount" class="text-sm font-bold text-pink-500 bg-pink-50 px-3 py-1 rounded-full"></span>
      </h2>
      <div class="flex gap-2">
        <button onclick="filterPhotos('Alle')" class="filter-btn active text-xs font-bold px-3 py-1.5 rounded-full bg-pink-600 text-white">Alle</button>
        <button onclick="filterPhotos('Training')" class="filter-btn text-xs font-bold px-3 py-1.5 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200">Training</button>
        <button onclick="filterPhotos('Wettkämpfe')" class="filter-btn text-xs font-bold px-3 py-1.5 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200">Wettkämpfe</button>
        <button onclick="filterPhotos('Gürtelprüfungen')" class="filter-btn text-xs font-bold px-3 py-1.5 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200">Gürtel</button>
      </div>
    </div>
    <div id="photoGrid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
      <div class="col-span-full text-center py-16 text-gray-300">
        <div class="text-5xl mb-3">📭</div>
        <p class="font-bold">Noch keine Fotos</p>
        <p class="text-sm">Lad dein erstes Foto hoch!</p>
      </div>
    </div>
  </section>
</main>

<!-- Edit Modal -->
<div id="editModal" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
  <div class="bg-white rounded-3xl p-8 w-full max-w-md shadow-2xl">
    <h3 class="text-xl font-black text-gray-800 mb-6">✏️ Foto bearbeiten</h3>
    <input type="hidden" id="editId"/>
    <div class="space-y-4">
      <div>
        <label class="block text-sm font-bold text-gray-600 mb-2">Titel</label>
        <input id="editTitle" type="text"
          class="w-full border-2 border-pink-100 rounded-xl px-4 py-3 focus:outline-none focus:border-pink-400 text-gray-700"/>
      </div>
      <div>
        <label class="block text-sm font-bold text-gray-600 mb-2">Kategorie</label>
        <select id="editKat"
          class="w-full border-2 border-pink-100 rounded-xl px-4 py-3 focus:outline-none focus:border-pink-400 text-gray-700">
          <option>Training</option>
          <option>Wettkämpfe</option>
          <option>Gürtelprüfungen</option>
        </select>
      </div>
    </div>
    <div class="flex gap-3 mt-8">
      <button onclick="saveEdit()" class="flex-1 py-3 rounded-xl font-black bg-gradient-to-r from-pink-500 to-purple-600 text-white hover:scale-[1.02] active:scale-95 transition-transform">💾 Speichern</button>
      <button onclick="closeModal()" class="px-6 py-3 rounded-xl font-bold bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors">Abbrechen</button>
    </div>
  </div>
</div>

<!-- Toast -->
<div id="toast" class="hidden fixed bottom-6 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-6 py-3 rounded-full font-bold text-sm shadow-xl z-50"></div>

<script>
  let selectedKat = 'Training';
  let allPhotos = [];
  let currentFilter = 'Alle';
  let pendingFiles = [];  // speichert Dateien aus Drag&Drop UND file input

  function setKat(btn, kat) {
    selectedKat = kat;
    document.querySelectorAll('.kategorie-btn').forEach(b => {
      b.classList.remove('active');
      b.classList.add('bg-white', 'text-gray-600');
    });
    btn.classList.add('active');
    btn.classList.remove('bg-white', 'text-gray-600');
  }

  // Drop-Zone
  const dz = document.getElementById('dropZone');
  const fi = document.getElementById('fileInput');
  dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('drag-over'); });
  dz.addEventListener('dragleave', () => dz.classList.remove('drag-over'));
  dz.addEventListener('drop', e => {
    e.preventDefault();
    dz.classList.remove('drag-over');
    pendingFiles = Array.from(e.dataTransfer.files).filter(f => f.type.startsWith('image/'));
    showPreviews(pendingFiles);
  });
  fi.addEventListener('change', () => {
    pendingFiles = Array.from(fi.files);
    showPreviews(pendingFiles);
  });

  function showPreviews(files) {
    const list = document.getElementById('previewList');
    list.innerHTML = '';
    if (!files.length) { list.classList.add('hidden'); document.getElementById('uploadBtn').disabled = true; return; }
    list.classList.remove('hidden');
    document.getElementById('uploadBtn').disabled = false;
    files.forEach(f => {
      const url = URL.createObjectURL(f);
      const div = document.createElement('div');
      div.className = 'aspect-square rounded-xl overflow-hidden bg-gray-100 relative';
      div.innerHTML = \`<img src="\${url}" class="w-full h-full object-cover"/>\`;
      list.appendChild(div);
    });
  }

  async function startUpload() {
    const files = pendingFiles;
    if (!files.length) return;

    const btn = document.getElementById('uploadBtn');
    const wrap = document.getElementById('progressWrap');
    const bar = document.getElementById('progressBar');
    const txt = document.getElementById('progressText');

    btn.disabled = true;
    wrap.classList.remove('hidden');

    const fd = new FormData();
    Array.from(files).forEach(f => fd.append('photos', f));
    fd.append('title', document.getElementById('uploadTitle').value);
    fd.append('kategorie', selectedKat);

    // Simulate progress during upload
    let prog = 0;
    const ticker = setInterval(() => {
      prog = Math.min(prog + 3, 90);
      bar.style.width = prog + '%';
    }, 100);

    try {
      const r = await fetch('/api/upload', { method: 'POST', body: fd });
      clearInterval(ticker);
      bar.style.width = '100%';
      txt.textContent = 'Fertig! ✨';

      if (r.ok) {
        showToast('✅ ' + files.length + ' Foto(s) hochgeladen!');
        fi.value = '';
        pendingFiles = [];
        document.getElementById('previewList').innerHTML = '';
        document.getElementById('previewList').classList.add('hidden');
        document.getElementById('uploadTitle').value = '';
        setTimeout(() => { wrap.classList.add('hidden'); bar.style.width = '0%'; btn.disabled = false; }, 1500);
        loadPhotos();
      } else {
        showToast('❌ Fehler beim Hochladen');
        btn.disabled = false;
      }
    } catch(e) {
      clearInterval(ticker);
      showToast('❌ Verbindungsfehler');
      btn.disabled = false;
    }
  }

  async function loadPhotos() {
    const r = await fetch('/api/photos');
    const data = await r.json();
    allPhotos = data.photos || [];
    renderPhotos();
  }

  function renderPhotos() {
    const grid = document.getElementById('photoGrid');
    const count = document.getElementById('photoCount');
    const filtered = currentFilter === 'Alle' ? allPhotos : allPhotos.filter(p => p.kategorie === currentFilter);

    count.textContent = allPhotos.length + ' Fotos';

    if (!filtered.length) {
      grid.innerHTML = \`<div class="col-span-full text-center py-16 text-gray-300">
        <div class="text-5xl mb-3">\${currentFilter === 'Alle' ? '📭' : '🔍'}</div>
        <p class="font-bold">\${currentFilter === 'Alle' ? 'Noch keine Fotos' : 'Keine Fotos in dieser Kategorie'}</p>
        \${currentFilter === 'Alle' ? '<p class="text-sm">Lad dein erstes Foto hoch!</p>' : ''}
      </div>\`;
      return;
    }

    const katColors = { 'Training': 'bg-pink-100 text-pink-700', 'Wettkämpfe': 'bg-purple-100 text-purple-700', 'Gürtelprüfungen': 'bg-amber-100 text-amber-700' };

    grid.innerHTML = filtered.map(p => \`
      <div class="photo-card group relative bg-white rounded-2xl overflow-hidden shadow-sm border border-gray-100" data-id="\${p.id}" data-kat="\${p.kategorie}">
        <div class="aspect-square overflow-hidden">
          <img src="/images/\${p.thumb}" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" alt="\${p.title}"/>
        </div>
        <div class="p-3">
          <p class="font-bold text-gray-800 text-sm truncate mb-1">\${p.title}</p>
          <span class="text-[10px] font-bold px-2 py-0.5 rounded-full \${katColors[p.kategorie] || 'bg-gray-100 text-gray-600'}">\${p.kategorie}</span>
        </div>
        <div class="absolute top-2 right-2 flex gap-1.5 opacity-0 group-hover:opacity-100 transition-opacity">
          <button onclick="openEdit('\${p.id}',\`\${p.title}\`,'\${p.kategorie}')"
            class="w-8 h-8 bg-white rounded-full shadow-md flex items-center justify-center text-base hover:scale-110 transition-transform">✏️</button>
          <button onclick="deletePhoto('\${p.id}')"
            class="w-8 h-8 bg-red-500 rounded-full shadow-md flex items-center justify-center text-base hover:scale-110 transition-transform">🗑️</button>
        </div>
      </div>
    \`).join('');
  }

  function filterPhotos(kat) {
    currentFilter = kat;
    document.querySelectorAll('.filter-btn').forEach(b => {
      b.classList.remove('bg-pink-600', 'text-white');
      b.classList.add('bg-gray-100', 'text-gray-600');
    });
    event.target.classList.add('bg-pink-600', 'text-white');
    event.target.classList.remove('bg-gray-100', 'text-gray-600');
    renderPhotos();
  }

  function openEdit(id, title, kat) {
    document.getElementById('editId').value = id;
    document.getElementById('editTitle').value = title;
    document.getElementById('editKat').value = kat;
    document.getElementById('editModal').classList.remove('hidden');
  }

  function closeModal() {
    document.getElementById('editModal').classList.add('hidden');
  }

  async function saveEdit() {
    const id = document.getElementById('editId').value;
    const r = await fetch('/api/photo/' + id, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title: document.getElementById('editTitle').value,
        kategorie: document.getElementById('editKat').value
      })
    });
    if (r.ok) {
      closeModal();
      showToast('✅ Gespeichert!');
      loadPhotos();
    }
  }

  async function deletePhoto(id) {
    if (!confirm('Foto wirklich löschen? Das kann nicht rückgängig gemacht werden.')) return;
    const r = await fetch('/api/photo/' + id, { method: 'DELETE' });
    if (r.ok) {
      showToast('🗑️ Foto gelöscht');
      loadPhotos();
    }
  }

  function showToast(msg) {
    const t = document.getElementById('toast');
    t.textContent = msg;
    t.classList.remove('hidden');
    setTimeout(() => t.classList.add('hidden'), 3000);
  }

  // Enter in Edit-Modal
  document.addEventListener('keydown', e => {
    if (e.key === 'Escape') closeModal();
    if (e.key === 'Enter' && !document.getElementById('editModal').classList.contains('hidden')) saveEdit();
  });

  loadPhotos();
</script>
</body>
</html>`);
});

app.listen(PORT, () => {
  console.log(`\n✨ MiyaKarate Admin läuft auf http://localhost:${PORT}\n`);
});
