From 4b03448e29a56b66729dbde2f9d5e42b4e607227 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Tue, 28 Apr 2026 01:10:36 +0200 Subject: [PATCH] fix(feedback): Screenshot scharf + ohne Feedback-UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auflösung: scale = window.devicePixelRatio (statt min:2 cap) — Retina-scharf - Vor dem html2canvas-Capture werden v2-feedback-{modal,overlay,btn} auf display:none gesetzt; finally-Block stellt UI zurueck. Damit ist die ausgegraute Modal-Schicht nicht im Bild - Capture nur des sichtbaren Viewports (width/height/x/y/windowWidth/Height explizit), spart Bandbreite + zeigt was der User wirklich sieht - MAX_W 800 -> 1600, JPEG 0.7 -> 0.85, imageSmoothingQuality high - requestAnimationFrame x2 vor capture, damit Browser den Reflow vor dem Snap fertig hat - app_version 1.0.1 -> 1.0.2 (Cache-Buster) Co-Authored-By: Claude Opus 4.7 (1M context) --- app/config.py | 2 +- .../v2/components/feedback_widget.html | 40 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/config.py b/app/config.py index 87935db..8632f74 100644 --- a/app/config.py +++ b/app/config.py @@ -4,7 +4,7 @@ from pathlib import Path class Settings(BaseSettings): app_name: str = "GWÖ-Antragsprüfer" - app_version: str = "1.0.1" + app_version: str = "1.0.2" prompt_version: str = "v4.1" # Paths diff --git a/app/templates/v2/components/feedback_widget.html b/app/templates/v2/components/feedback_widget.html index 5d89dab..bc2e862 100644 --- a/app/templates/v2/components/feedback_widget.html +++ b/app/templates/v2/components/feedback_widget.html @@ -298,15 +298,39 @@ // Screenshot (optional, via html2canvas) if (screenshot && window.html2canvas) { submitBtn.textContent = 'Screenshot wird erstellt…'; + // Modal + Overlay verstecken, damit der Screenshot die Seite ohne + // Feedback-UI zeigt. Nach dem Capture wieder einblenden. + var modal = document.getElementById('v2-feedback-modal'); + var overlay = document.getElementById('v2-feedback-overlay'); + var fbBtn = document.getElementById('v2-feedback-btn'); + var prev = { + modalDisp: modal ? modal.style.display : null, + overlayDisp: overlay ? overlay.style.display : null, + btnDisp: fbBtn ? fbBtn.style.display : null, + }; + if (modal) modal.style.display = 'none'; + if (overlay) overlay.style.display = 'none'; + if (fbBtn) fbBtn.style.display = 'none'; + // ein Frame warten, damit die Browser den Reflow rendert + await new Promise(function (r) { requestAnimationFrame(function(){ requestAnimationFrame(r); }); }); try { var canvas = await window.html2canvas(document.body, { - scale: Math.min(window.devicePixelRatio || 1, 2), + scale: window.devicePixelRatio || 2, // Hi-DPI: scharfes Bild useCORS: true, allowTaint: false, logging: false, + backgroundColor: getComputedStyle(document.body).backgroundColor || '#fff', + // Sichtbares Viewport, nicht das ganze Dokument + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, + x: window.scrollX, + y: window.scrollY, + windowWidth: document.documentElement.clientWidth, + windowHeight: document.documentElement.clientHeight, }); - // Breite auf max 800 px beschränken - var MAX_W = 800; + // Breite begrenzen — bei Hi-DPI Display kann canvas.width 4000+ sein. + // Cap bei 1600 logischen px (à la Retina-friendly), JPEG quality 0.85. + var MAX_W = 1600; var finalCanvas = canvas; if (canvas.width > MAX_W) { var ratio = MAX_W / canvas.width; @@ -314,14 +338,20 @@ sc.width = MAX_W; sc.height = Math.round(canvas.height * ratio); var ctx = sc.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; ctx.drawImage(canvas, 0, 0, sc.width, sc.height); finalCanvas = sc; } - var dataUrl = finalCanvas.toDataURL('image/jpeg', 0.7); + var dataUrl = finalCanvas.toDataURL('image/jpeg', 0.85); fd.append('screenshot', dataUrl); } catch (err) { - // Screenshot fehlgeschlagen → trotzdem absenden fd.append('screenshot_error', String(err)); + } finally { + // UI zurückbringen + if (modal) modal.style.display = prev.modalDisp || ''; + if (overlay) overlay.style.display = prev.overlayDisp || ''; + if (fbBtn) fbBtn.style.display = prev.btnDisp || ''; } }