diff --git a/README.md b/README.md index 8b1bf25..4f0f60c 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,10 @@ Admin-Passwort für dich, und pro Freund ein frei wählbares Freigabe-Passwort z - **/** – Login mit dem Admin-Passwort. - **/admin** – Übersicht: Freunde anlegen, bearbeiten, Freigabe verwalten, löschen. - **/friend/\** – Freund bearbeiten (Profilbild, Steckbrief, Blöcke) – für dich als Admin. + Ein "👀 Vorschau"-Button zeigt, wie die Seite später im Freundesbuch aussehen wird. - **/share/\** – der Link, den du deinem Freund gibst. Er meldet sich mit dem von dir - vergebenen Freigabe-Passwort an und kann **nur** dort etwas eintragen. + vergebenen Freigabe-Passwort an und kann **nur** dort etwas eintragen – inklusive der + gleichen Vorschau-Funktion. - **/book** – die verspielte Freundesbuch-Ansicht mit Blätterfunktion und PDF-Export (Button oben rechts). - Im Admin-Bereich gibt es oben zwei Reiter: **Freunde** (`/admin`, Standardansicht) und diff --git a/backend/package-lock.json b/backend/package-lock.json index 952de75..19bcc74 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -457,13 +457,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", - "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.0.tgz", + "integrity": "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A==", "license": "MIT", "optional": true, "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/yauzl": { @@ -2708,9 +2708,9 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "license": "MIT", "optional": true }, diff --git a/backend/public/book/book.css b/backend/public/book/book.css index ae2adcb..301bbeb 100644 --- a/backend/public/book/book.css +++ b/backend/public/book/book.css @@ -32,7 +32,7 @@ .scrap-item:hover { transform: scale(1.04) rotate(0deg) !important; z-index: 2; } .scrap-item.polaroid { padding: 10px 10px 30px; width: 220px; } -.scrap-item.polaroid img { width: 100%; height: 190px; object-fit: cover; display: block; } +.scrap-item.polaroid img { display: block; max-width: 100%; max-height: 260px; width: auto; height: auto; margin: 0 auto; } .scrap-item.text-note { width: 220px; min-height: 160px; padding: 1.2em; background: #fef7d6; font-family: Georgia, serif; } @@ -61,6 +61,9 @@ box-shadow: var(--shadow); overflow: hidden; border: 2px dashed var(--mustard); + display: flex; + align-items: center; + justify-content: center; } .cover-stickers { position: absolute; inset: 0; } .cover-sticker { @@ -75,8 +78,6 @@ position: relative; z-index: 5; text-align: center; - top: 42%; - transform: translateY(-50%); padding: 0.6em 1.2em; } .cover-title { diff --git a/backend/public/friend/friend.css b/backend/public/friend/friend.css index 7be9fcd..1e54bdb 100644 --- a/backend/public/friend/friend.css +++ b/backend/public/friend/friend.css @@ -26,7 +26,7 @@ background: var(--paper-2); border-radius: 10px; box-shadow: var(--shadow); padding: 1em; width: 220px; position: relative; } -.block-item img { width: 100%; height: 140px; object-fit: cover; border-radius: 6px; } +.block-item img { display: block; max-width: 100%; max-height: 220px; width: auto; height: auto; margin: 0 auto; border-radius: 6px; } .block-item audio { width: 100%; margin-top: 0.4em; } .block-item textarea { min-height: 4em; font-size: 0.9rem; } .block-item .block-caption { font-size: 0.82rem; margin-top: 0.5em; } @@ -36,3 +36,23 @@ position: absolute; top: -8px; left: 10px; font-size: 0.7rem; font-weight: 700; background: var(--mustard); color: #533; padding: 0.1em 0.6em; border-radius: 999px; } + +.preview-modal-backdrop { + position: fixed; inset: 0; background: rgba(75,59,107,0.45); + display: flex; align-items: flex-start; justify-content: center; + z-index: 100; padding: 2em 1em; overflow-y: auto; +} +.preview-modal { + background: var(--paper); + border-radius: 16px; + box-shadow: 0 12px 40px rgba(0,0,0,0.3); + width: 100%; max-width: 780px; + max-height: calc(100vh - 4em); + display: flex; flex-direction: column; overflow: hidden; +} +.preview-modal-head { + display: flex; align-items: center; justify-content: space-between; + gap: 1em; padding: 1em 1.4em; background: var(--paper-2); + border-bottom: 2px solid var(--line); flex-wrap: wrap; +} +.preview-modal-body { overflow-y: auto; padding: 0.5em; } diff --git a/backend/public/friend/friend.js b/backend/public/friend/friend.js index 864a741..e5b8283 100644 --- a/backend/public/friend/friend.js +++ b/backend/public/friend/friend.js @@ -278,4 +278,80 @@ async function moveBlock(id, dir) { if (res.ok) { currentFriend = data.friend; renderBlocks(); } } +// --- Vorschau: rendert die Seite genauso wie später im Freundesbuch-Viewer --- +function randomRotation(seed) { + const angles = [-6, -4, -2, 2, 4, 6, -3, 3, 5, -5]; + return angles[seed % angles.length]; +} + +function renderPreview() { + const f = currentFriend; + const container = document.getElementById('previewContent'); + + const profileHtml = f.profileImage + ? `` + : `
${(f.name || '?')[0].toUpperCase()}
`; + + const sbEntries = STECKBRIEF_FIELDS + .map(([key, label]) => [label, (f.steckbrief || {})[key]]) + .filter(([, v]) => v && String(v).trim()); + const sbHtml = sbEntries.length + ? `
${sbEntries.map(([label, v]) => ` +
${escapeHtml(label)}${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.

'; + + container.innerHTML = ` +
+
+
+ ${profileHtml} +

${escapeHtml(f.name)}

+
+
+

Steckbrief

+ ${sbHtml} +
+
+ ${blocksHtml} +
+
+ `; +} + +document.getElementById('previewBtn').addEventListener('click', () => { + renderPreview(); + document.getElementById('previewModal').style.display = 'flex'; +}); +document.getElementById('previewCloseBtn').addEventListener('click', () => { + document.getElementById('previewModal').style.display = 'none'; +}); +document.getElementById('previewModal').addEventListener('click', (e) => { + if (e.target.id === 'previewModal') document.getElementById('previewModal').style.display = 'none'; +}); + init(); diff --git a/backend/public/friend/index.html b/backend/public/friend/index.html index 9a305c3..152ccb4 100644 --- a/backend/public/friend/index.html +++ b/backend/public/friend/index.html @@ -6,6 +6,7 @@ Freundesbuch + @@ -26,7 +27,10 @@ + + + diff --git a/backend/src/pdf.js b/backend/src/pdf.js index 43c6eeb..1eaa383 100644 --- a/backend/src/pdf.js +++ b/backend/src/pdf.js @@ -212,7 +212,7 @@ async function buildFullHtml(friends, bookTitle) { .blocks { display: flex; flex-wrap: wrap; gap: 6mm; } .pdf-block { background: #fff; padding: 4mm; box-shadow: 0 3px 10px rgba(0,0,0,0.15); } .polaroid { padding: 4mm 4mm 10mm; width: 58mm; } - .polaroid img { width: 100%; height: 48mm; object-fit: cover; display: block; } + .polaroid img { display: block; max-width: 100%; max-height: 55mm; width: auto; height: auto; margin: 0 auto; } .text-block { width: 58mm; min-height: 40mm; background: #fef7d6; font-family: Georgia, serif; font-size: 12px; } .audio-block { width: 58mm; min-height: 30mm; background: #e8f3ec; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; font-size: 13px; padding: 3mm; } .audio-cover { width: 100%; height: 30mm; object-fit: cover; border-radius: 4px; margin-bottom: 2mm; }