diff --git a/src/lib/productCard.ts b/src/lib/productCard.ts index 4bbb73c..0fc63bb 100644 --- a/src/lib/productCard.ts +++ b/src/lib/productCard.ts @@ -5,7 +5,7 @@ // V editore (mode 'preview') sa vizuál skladá živo z HTML/CSS. Pri exporte (ZIP/Brevo) sa vizuál z náhľadu // uloží ako JPG (src/lib/cardImages.ts) a do e-mailu ide ako jeden obrázok – rovnako ako karta_produkt_*.jpg // v šablóne. Vďaka tomu karta vyzerá rovnako vo všetkých klientoch (Gmail, Outlook, mobil). -import { CardProduct, NewsletterModule } from '@/types/newsletter'; +import { BadgeTemplate, CardProduct, NewsletterModule } from '@/types/newsletter'; import { renderCleanBadgeSvg, BadgeProductData } from './badgeUtils'; import { SUPABASE_ASSETS_BASE } from './defaultCampaign'; import { pickAlt } from './altText'; @@ -217,6 +217,45 @@ export function cardVisualKey(p: CardProduct, ctx: CardContext): string { } // Vizuál karty v náhľade: fotka (zoom/posun), cenovka vpravo dole, štítky nad cenovkou +// Viditeľné pozadie cenovky v SVG môže mať okolo seba priehľadný okraj. Štítky predajní ukotvujeme +// k pravému hornému rohu samotného pozadia, preto jeho hranice odmeriame v prehliadači (getBBox). +// Výsledok je podiel rozmerov viewBoxu; na serveri (bez DOM) sú okraje 0. +interface BadgeBgBox { + top: number; // okraj zhora / výška + right: number; // okraj sprava / šírka + aspect: number; // výška / šírka SVG +} +const badgeBgCache = new Map(); + +function badgeBackgroundBox(t: BadgeTemplate): BadgeBgBox | null { + if (!t.svgRaw || typeof document === 'undefined') return null; + const cached = badgeBgCache.get(t.svgRaw); + if (cached) return cached; + let box: BadgeBgBox | null = null; + const holder = document.createElement('div'); + holder.style.cssText = 'position: absolute; left: -10000px; top: 0; visibility: hidden;'; + try { + holder.innerHTML = renderCleanBadgeSvg({ ...t, layersConfig: [] }, { priceMain: '', priceSub: '' }, 200); + document.body.appendChild(holder); + const svg = holder.querySelector('svg'); + const vb = svg?.viewBox.baseVal; + const bb = svg?.getBBox(); + if (vb && bb && vb.width && vb.height && bb.width && bb.height) { + box = { + top: Math.max(0, (bb.y - vb.y) / vb.height), + right: Math.max(0, (vb.x + vb.width - (bb.x + bb.width)) / vb.width), + aspect: vb.height / vb.width + }; + } + } catch { + box = null; + } finally { + holder.remove(); + } + if (box) badgeBgCache.set(t.svgRaw, box); + return box; +} + function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): string { const height = cardVisualHeight(ctx.cardWidth); const bg = p.cardBgColor || ctx.cardBg; @@ -227,11 +266,15 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str const sx = Math.round(p.storesOffsetX || 0); const sy = Math.round(p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y); const stores = renderStores(p, ctx.mode, Math.max(10, Math.round(bw * 0.12))); + // Pravý horný roh viditeľného pozadia cenovky (pri PNG a predvolenej cenovke = roh cenovky) + const bgBox = p.badgeCustomTemplate && !p.customBadgePngUrl ? badgeBackgroundBox(p.badgeCustomTemplate) : null; + const insetTop = bgBox ? Math.round(bgBox.top * bw * bgBox.aspect) : 0; + const insetRight = bgBox ? Math.round(bgBox.right * bw) : 0; return `
${p.imageUrl ? `` : ''}
- ${stores ? `
${stores}
` : ''} + ${stores ? `
${stores}
` : ''} ${renderBadge(p, ctx, bw)}
`;