Vorschau-Kuli in der App: identisch zu Directus (blau, 1.4, dezenter Wobble-Filter mit eindeutiger ID pro Inline-SVG), weisser Grund, PNG in natuerlicher Groesse
All checks were successful
Build & Deploy / deploy (push) Successful in 43s

This commit is contained in:
Lucas Orth
2026-08-24 13:45:13 +02:00
parent 32aa0a549f
commit 5f40d0fd5b
2 changed files with 28 additions and 12 deletions

View File

@@ -13,6 +13,23 @@ import { useEffect, useMemo, useState } from 'react';
export type PreviewDoc = { art: 'brief' | 'umschlag'; svg: string };
// Vorschau-Optik „Kugelschreiber" – IDENTISCH zu Directus (neuauftrag): blaue
// Tinte, etwas dicker (1.4) + dezenter Wobble (feTurbulence/Displacement) für die
// typische Kuli-Linie. non-scaling-stroke bleibt (Glyphen skalieren per transform),
// weißer Grund. Nur Anzeige/Download – die an die Produktion gehenden SVGs bleiben
// schwarz. Inline-SVGs brauchen eine EINDEUTIGE Filter-ID pro Dokument.
let inkSeq = 0;
const inkify = (svg: string): string => {
if (typeof svg !== 'string') return svg;
const id = `skink${inkSeq++}`;
return svg
.replace(/stroke="#0{3,6}"/gi, 'stroke="#1a2a6c" stroke-opacity="0.95"')
.replace(/stroke-width="0\.6"/gi, 'stroke-width="1.4"')
.replace(/(<svg[^>]*>)/i, `$1<defs><filter id="${id}" x="-4%" y="-4%" width="108%" height="108%" color-interpolation-filters="sRGB"><feTurbulence type="fractalNoise" baseFrequency="0.05" numOctaves="1" seed="4" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="1.3" xChannelSelector="R" yChannelSelector="G"/></filter></defs>`)
.replace(/(<rect[^>]*fill="#FFFFFF"[^>]*\/>)/i, `$1<g filter="url(#${id})">`)
.replace(/<\/svg>\s*$/i, '</g></svg>');
};
/** Ein Empfänger-Dokument: Schriftstück + optional zugehöriger Umschlag. */
type Gruppe = { brief?: PreviewDoc; umschlag?: PreviewDoc };
@@ -81,10 +98,12 @@ export default function PreviewPanel({
const m = zoomSvg.match(/viewBox="0 0 ([\d.]+) ([\d.]+)"/);
const w = Math.round(m ? Number(m[1]) : 794);
const h = Math.round(m ? Number(m[2]) : 1123);
const scale = 2; // für ein scharfes Musterbild
// Gespeicherte Vorschau in Kugelschreiber-Blau (wie die Anzeige); die
// an die Produktion gehenden SVGs bleiben davon unberührt (schwarz).
const inkSvg = zoomSvg.replace(/stroke="#0{3,6}"/gi, 'stroke="#1a2a6c"');
// Natürliche Größe (nicht 2×): non-scaling-stroke wirkt konstant in
// Gerätepixeln – bei 2× würde die Linie relativ dünn. So sieht das PNG aus
// wie die Großansicht. Gespeicherte Vorschau in Kuli-Optik (wie Anzeige);
// die an die Produktion gehenden SVGs bleiben schwarz.
const scale = 1;
const inkSvg = inkify(zoomSvg);
const url = URL.createObjectURL(new Blob([inkSvg], { type: 'image/svg+xml;charset=utf-8' }));
const img = new Image();
img.onload = () => {
@@ -142,7 +161,7 @@ export default function PreviewPanel({
>
<figcaption className="sk-preview-doc-label">{label}</figcaption>
{/* Das SVG stammt aus unserer eigenen Generierung, nicht aus Nutzereingaben. */}
<div className="sk-preview-doc-sheet" dangerouslySetInnerHTML={{ __html: svg }} />
<div className="sk-preview-doc-sheet" dangerouslySetInnerHTML={{ __html: inkify(svg) }} />
</figure>
);
@@ -199,7 +218,7 @@ export default function PreviewPanel({
aria-label="Vergrößerte Vorschau">
<div className="sk-lightbox-sheet" style={{ aspectRatio: seitenverhaeltnis(zoomSvg) }}
onClick={(e) => e.stopPropagation()}
dangerouslySetInnerHTML={{ __html: zoomSvg }} />
dangerouslySetInnerHTML={{ __html: inkify(zoomSvg) }} />
<div className="sk-lightbox-actions">
<button className="sk-lightbox-close" onClick={(e) => { e.stopPropagation(); bildSpeichern(); }}>
Bild speichern

View File

@@ -460,21 +460,18 @@ button { font-family: inherit; }
text-transform: uppercase;
color: var(--sk-muted);
}
/* Kugelschreiber-Optik (blau, dickere Striche, Wobble) kommt aus inkify();
der Untergrund ist weiß (das SVG bringt seinen weißen Grund selbst mit). */
.sk-preview-doc-sheet {
border: 1px solid var(--sk-border);
border-radius: var(--sk-radius);
background: var(--sk-bg-1);
background: var(--sk-white);
padding: var(--sk-space-2);
overflow: hidden;
}
.sk-preview-doc-sheet svg { display: block; width: 100%; height: auto; }
.sk-preview-doc:hover .sk-preview-doc-sheet { border-color: var(--sk-accent); }
/* Vorschau-Optik „Kugelschreiber": nur die Anzeige färbt die schwarzen Striche
in Kuli-Blau. Die SVG-Daten (und damit die Produktion) bleiben schwarz. */
.sk-preview-doc-sheet svg [stroke],
.sk-lightbox-sheet svg [stroke] { stroke: #1a2a6c; }
/* ── Blättern: Pfeile als klare Buttons + deutliche Nummer ─ */
.sk-preview-pager {
display: flex;