fix(feedback): Screenshot scharf + ohne Feedback-UI

- 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) <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-04-28 01:10:36 +02:00
parent 07bb832c35
commit 4b03448e29
2 changed files with 36 additions and 6 deletions

View File

@ -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

View File

@ -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 || '';
}
}