let friends = []; let bookTitle = 'Unser Freundesbuch'; let currentIndex = -1; // -1 = Deckblatt, 0..n-1 = Freunde const STECKBRIEF_LABELS = { spitzname: 'Spitzname', geburtstag: 'Geburtstag', adresse: 'Adresse / Wohnort', lieblingsfarbe: 'Lieblingsfarbe', lieblingsessen: 'Lieblingsessen', lieblingstier: 'Lieblingstier', hobbys: 'Hobbys', lieblingsmusik: 'Lieblingsmusik', lieblingsfilm: 'Lieblingsfilm/-serie', traumberuf: 'Traumberuf', bestefreunde: 'Beste Freunde', motto: 'Motto / Lebensweisheit', sonstiges: 'Sonstiges', }; const COVER_MAX_STICKERS = 24; function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str || ''; return div.innerHTML; } function randomRotation(seed) { const angles = [-6, -4, -2, 2, 4, 6, -3, 3, 5, -5]; return angles[seed % angles.length]; } // Einfacher, deterministischer Pseudo-Zufallsgenerator (gleiche Seed -> gleiches Ergebnis), // damit das Deckblatt bei jedem Aufruf ähnlich aussieht statt bei jedem Reload zu "springen". function seededRandom(seed) { let t = (seed += 0x6d2b79f5); t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } function uploadUrl(folder, filename) { return `/uploads/${encodeURIComponent(folder)}/${encodeURIComponent(filename)}`; } async function load() { const [bookRes, settingsRes] = await Promise.all([fetch('/api/book'), fetch('/api/settings')]); if (bookRes.status === 401 || settingsRes.status === 401) { window.location.href = '/'; return; } const bookData = await bookRes.json(); const settingsData = await settingsRes.json(); friends = bookData.friends || []; bookTitle = (settingsData.settings && settingsData.settings.bookTitle) || 'Unser Freundesbuch'; document.getElementById('bookNav').style.display = 'flex'; renderPage(); } // --- Deckblatt: Titel + "sticker-gebombte" Foto-Collage --- function collectStickerImages() { const imgs = []; friends.forEach((f) => { if (f.profileImage) imgs.push({ folder: f.folderName, filename: f.profileImage }); (f.blocks || []).forEach((b) => { if (b.type === 'image' && b.filename) imgs.push({ folder: f.folderName, filename: b.filename }); if (b.type === 'audio' && b.coverFilename) imgs.push({ folder: f.folderName, filename: b.coverFilename }); }); }); return imgs; } function renderCoverPage() { const root = document.getElementById('bookRoot'); let imgs = collectStickerImages(); if (imgs.length > COVER_MAX_STICKERS) { const step = imgs.length / COVER_MAX_STICKERS; imgs = Array.from({ length: COVER_MAX_STICKERS }, (_, i) => imgs[Math.floor(i * step)]); } let stickersHtml = ''; if (imgs.length) { const cols = Math.min(6, Math.max(3, Math.ceil(Math.sqrt(imgs.length * 1.6)))); const rows = Math.ceil(imgs.length / cols); stickersHtml = imgs .map((img, i) => { const cellX = i % cols; const cellY = Math.floor(i / cols); const rx = seededRandom(i * 13 + 1); const ry = seededRandom(i * 17 + 2); const rr = seededRandom(i * 7 + 3); const rs = seededRandom(i * 5 + 4); const leftPct = ((cellX + 0.1 + rx * 0.8) / cols) * 100; const topPct = ((cellY + 0.1 + ry * 0.8) / rows) * 100; const rot = (rr * 50 - 25).toFixed(1); const size = Math.round(85 + rs * 65); return ``; }) .join(''); } root.innerHTML = `
${stickersHtml}

${escapeHtml(bookTitle)}

`; } function renderFriendPage(f) { const root = document.getElementById('bookRoot'); const profileHtml = f.profileImage ? `` : `
${(f.name || '?')[0].toUpperCase()}
`; const sbEntries = Object.entries(f.steckbrief || {}).filter(([, v]) => v && String(v).trim()); const sbHtml = sbEntries.length ? `
${sbEntries.map(([k, v]) => `
${escapeHtml(STECKBRIEF_LABELS[k] || k)}${escapeHtml(v)}
`).join('')}
` : `

Noch nichts eingetragen.

`; const blocks = [...(f.blocks || [])].sort((a, b) => a.order - b.order); const blocksHtml = blocks.length ? blocks.map((b, i) => { const rot = randomRotation(i + f.id.length); if (b.type === 'image') { return `
${b.caption ? `
${escapeHtml(b.caption)}
` : ''}
`; } if (b.type === 'audio') { return `
${b.coverFilename ? `` : ''}
🎙️${b.caption ? ' ' + escapeHtml(b.caption) : ''}
`; } return `

${escapeHtml(b.content).replace(/\n/g, '
')}

${b.caption ? `
${escapeHtml(b.caption)}
` : ''}
`; }).join('') : '

Noch keine Erinnerungen hinzugefügt.

'; root.innerHTML = `
${profileHtml}

${escapeHtml(f.name)}

Steckbrief

${sbHtml}
${blocksHtml}
`; } function renderPage() { const root = document.getElementById('bookRoot'); if (currentIndex === -1) { renderCoverPage(); } else if (!friends.length) { root.innerHTML = `

Noch ist dieses Buch leer 📖

Lege im Admin-Bereich Freunde an und teile den Link mit ihnen.

`; } else { renderFriendPage(friends[currentIndex]); } const indicator = document.getElementById('pageIndicator'); indicator.textContent = currentIndex === -1 ? 'Deckblatt' : `Seite ${currentIndex + 1} von ${friends.length}`; document.getElementById('prevBtn').disabled = currentIndex === -1; document.getElementById('nextBtn').disabled = currentIndex >= friends.length - 1; } document.getElementById('prevBtn').addEventListener('click', () => { if (currentIndex > -1) { currentIndex--; renderPage(); window.scrollTo({ top: 0, behavior: 'smooth' }); } }); document.getElementById('nextBtn').addEventListener('click', () => { if (currentIndex < friends.length - 1) { currentIndex++; renderPage(); window.scrollTo({ top: 0, behavior: 'smooth' }); } }); document.addEventListener('keydown', (e) => { if (e.key === 'ArrowLeft') document.getElementById('prevBtn').click(); if (e.key === 'ArrowRight') document.getElementById('nextBtn').click(); }); document.getElementById('pdfBtn').addEventListener('click', async () => { const btn = document.getElementById('pdfBtn'); btn.disabled = true; btn.textContent = 'Erzeuge PDF...'; try { const res = await fetch('/api/book/pdf'); if (!res.ok) throw new Error('PDF-Export fehlgeschlagen.'); const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'freundesbuch.pdf'; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } catch (err) { alert(err.message); } finally { btn.disabled = false; btn.textContent = '⬇️ Als PDF exportieren'; } }); load();