diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 4b5b7a40..1464fca3 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -829,8 +829,17 @@ router.get('/:slug/thumbnail/:photoId', 'X-Protected-Thumbnail': 'true' }); - // Send file - res.sendFile(path.resolve(thumbPath)); + // Check if watermarks are enabled and apply to thumbnail + const watermarkSettings = await watermarkService.getWatermarkSettings(); + + if (watermarkSettings && watermarkSettings.enabled) { + // Apply watermark to thumbnail + const watermarkedBuffer = await watermarkService.applyWatermark(thumbPath, watermarkSettings); + res.send(watermarkedBuffer); + } else { + // Send file without watermark + res.sendFile(path.resolve(thumbPath)); + } } catch (error) { logger.error('Error serving thumbnail:', { error: error.message, diff --git a/frontend/src/components/gallery/GalleryLayout.tsx b/frontend/src/components/gallery/GalleryLayout.tsx index 87738879..38dd25a8 100644 --- a/frontend/src/components/gallery/GalleryLayout.tsx +++ b/frontend/src/components/gallery/GalleryLayout.tsx @@ -366,7 +366,10 @@ export const GalleryLayout: React.FC = ({ className={`${heroLogoSize.className} w-auto object-contain mx-auto`} style={{ ...(heroLogoSize.style || {}), - filter: 'brightness(0) invert(1) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + // Only apply brightness/invert filter to default logo; custom logos display as-is + filter: brandingSettings?.logo_url + ? 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' + : 'brightness(0) invert(1) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3))' }} /> {shouldShowCompanyName() && brandingSettings?.company_name && ( diff --git a/frontend/src/components/gallery/GalleryView.tsx b/frontend/src/components/gallery/GalleryView.tsx index d22a2e24..333f2d08 100644 --- a/frontend/src/components/gallery/GalleryView.tsx +++ b/frontend/src/components/gallery/GalleryView.tsx @@ -380,12 +380,12 @@ export const GalleryView: React.FC = ({ slug, event }) => { } }); - // Transform URLs for watermarks if enabled + // Transform full-size URLs for watermarks if enabled + // Note: Thumbnails are watermarked server-side at the thumbnail endpoint if (watermarkEnabled) { photos = photos.map(photo => ({ ...photo, - url: `/gallery/${slug}/photo/${photo.id}`, - thumbnail_url: `/gallery/${slug}/photo/${photo.id}` // Use watermarked version for thumbnails too + url: `/gallery/${slug}/photo/${photo.id}` })); } diff --git a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx index d3ec0487..e06c725a 100644 --- a/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/HeroGalleryLayout.tsx @@ -142,8 +142,11 @@ export const HeroGalleryLayout: React.FC = ({ } alt="Event logo" className="h-20 sm:h-24 lg:h-32 mx-auto" - style={{ - filter: 'brightness(0) invert(1) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' + style={{ + // Only apply brightness/invert filter to default logo; custom logos display as-is + filter: eventLogo + ? 'drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' + : 'brightness(0) invert(1) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5))' }} /> diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 5f24300f..63f4c16a 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -778,6 +778,10 @@ "enableDevtoolsProtection": "Entwicklertools erkennen", "useCanvasRendering": "Canvas-Rendering (erweiterter Schutz)", "protectionInfo": "Schutzfunktionen helfen, unerlaubte Downloads zu verhindern, können jedoch nicht alle Methoden blockieren.", + "protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre", + "protectionLevelStandard": "Standard - Tastenkombinationen blockiert", + "protectionLevelEnhanced": "Erweitert - Bildschirmfoto-Erkennung", + "protectionLevelMaximum": "Maximum - DevTools-Erkennung & Canvas-Rendering", "heroPhoto": "Hero-Foto", "heroPhotoHelp": "Wählen Sie ein hervorgehobenes Foto für das Hero-Galerie-Layout", "selectHeroPhoto": "Hero-Foto auswählen", @@ -1032,6 +1036,48 @@ "saveSettings": "Sicherheitseinstellungen speichern", "saveSecuritySettings": "Sicherheitseinstellungen speichern" }, + "events": { + "title": "Veranstaltungserstellung", + "requiredFields": "Pflichtfelder", + "requiredFieldsDescription": "Konfigurieren Sie, welche Kontaktfelder beim Erstellen neuer Veranstaltungen erforderlich sind.", + "requireCustomerName": "Kundenname erforderlich", + "requireCustomerNameHelp": "Kundenname muss für neue Veranstaltungen angegeben werden", + "requireCustomerEmail": "Kunden-E-Mail erforderlich", + "requireCustomerEmailHelp": "Kunden-E-Mail muss für neue Veranstaltungen angegeben werden", + "customerEmailWarning": "Erforderlich für das Versenden von Galerie-Einladungen", + "requireAdminEmail": "Admin-E-Mail erforderlich", + "requireAdminEmailHelp": "Admin-E-Mail muss für neue Veranstaltungen angegeben werden", + "adminEmailWarning": "Erforderlich für den Erhalt von Veranstaltungsbenachrichtigungen", + "saveSettings": "Veranstaltungseinstellungen speichern", + "noteTitle": "Hinweis", + "noteText": "Diese Einstellungen betreffen nur die Erstellung neuer Veranstaltungen. Bestehende Veranstaltungen sind nicht betroffen. Standardmäßig sind alle Felder erforderlich." + }, + "imageSecurity": { + "title": "Bildschutz", + "saveSuccess": "Bildschutz-Einstellungen gespeichert", + "saveError": "Fehler beim Speichern der Einstellungen", + "loadError": "Fehler beim Laden der Bildschutz-Einstellungen", + "defaultProtection": "Standard-Schutzeinstellungen", + "defaultProtectionHelp": "Diese Einstellungen gelten für alle neuen Veranstaltungen. Einzelne Veranstaltungen können diese Standardwerte überschreiben.", + "protectionLevel": "Standard-Schutzstufe", + "imageQuality": "Standard-Bildqualität", + "fragmentationLevel": "Fragmentierungsstufe", + "enableDevtools": "DevTools-Erkennung standardmäßig aktivieren", + "enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)", + "rateLimiting": "Ratenbegrenzung", + "rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.", + "requestsPerMinute": "Anfragen pro Minute", + "requestsPer5Minutes": "Anfragen pro 5 Min.", + "requestsPerHour": "Anfragen pro Stunde", + "securityMonitoring": "Sicherheitsüberwachung", + "suspiciousThreshold": "Schwellenwert für verdächtige Aktivitäten", + "autoBlockThreshold": "Automatische Sperrschwelle", + "enableMonitoring": "Sicherheitsüberwachung aktivieren", + "blockSuspiciousIps": "Verdächtige IPs automatisch blockieren", + "logEvents": "Sicherheitsereignisse in Datenbank protokollieren", + "infoTitle": "Über Bildschutz", + "infoText": "Diese Schutzfunktionen helfen, gelegentliches Herunterladen und Kopieren zu verhindern, können aber nicht alle Methoden blockieren. Entschlossene Benutzer finden möglicherweise trotzdem Wege, Bilder zu erfassen. Erwägen Sie die Verwendung von Wasserzeichen und rechtlichen Vereinbarungen für umfassenden Schutz." + }, "categories": { "title": "Kategorien", "about": "Über Fotokategorien", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3cfdc0ba..080206a6 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -452,6 +452,10 @@ "enableDevtoolsProtection": "Detect developer tools", "useCanvasRendering": "Canvas rendering (advanced protection)", "protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.", + "protectionLevelBasic": "Basic - Right-click blocking only", + "protectionLevelStandard": "Standard - Keyboard shortcuts blocked", + "protectionLevelEnhanced": "Enhanced - Print screen detection", + "protectionLevelMaximum": "Maximum - DevTools detection & canvas rendering", "heroPhoto": "Hero Photo", "heroPhotoHelp": "Select a featured photo for the hero gallery layout", "selectHeroPhoto": "Select Hero Photo",