feat: okraj stitkov predajni tensi (polovica linky cenovky) so zaoblenymi vonkajsimi rohmi

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub_HostacnyandClaude Opus 5.5 committed 2026-10-21 16:37:05 +02:00
1 parent 5cbe981430
commit 2a0b1f37ba
1 file changed
+23 -18
+23 -18
View File
@@ -185,23 +185,28 @@ function renderDualSticker(p: CardProduct): string {
</table>`;
}
// Biely okraj okolo štítkov predajní ako skupiny (obrys celej skupiny, nie každého štítka zvlášť) – rovnako hrubý
// ako biela linka okolo pozadia cenovky: 4,5 px pri predvolenej šírke cenovky 130 px, mení sa spolu s cenovkou.
// Štyri biele tiene bez rozmazania (vľavo, vpravo, hore, dole) za sebou rozšíria tvar skupiny o hrúbku linky.
function storesOutline(badgeWidth: number): string {
const t = Math.round(((4.5 * badgeWidth) / 130) * 10) / 10;
return `filter: drop-shadow(${t}px 0 0 #fff) drop-shadow(-${t}px 0 0 #fff) drop-shadow(0 ${t}px 0 #fff) drop-shadow(0 -${t}px 0 #fff);`;
}
// Štítky predajní (Potraviny / Supermarket / Tempo) pod sebou; výška podľa veľkosti cenovky
function renderStores(p: CardProduct, mode: RenderMode, tagHeight: number): string {
const tag = (color: string, file: string, alt: string) =>
`<div style="text-align: right; line-height: 0;"><img src="${escapeHtml(assetUrl(SUPABASE_ASSETS_BASE + file, mode))}" height="${tagHeight}" alt="${alt}" style="display: inline-block; height: ${tagHeight}px; width: auto; border: 0; background-color: ${color};"></div>`;
return [
p.stores?.potraviny ? tag('#e2001a', 'stit_potraviny.png', 'Potraviny') : '',
p.stores?.supermarket ? tag('#f9890e', 'stit_supermarket.png', 'Supermarket') : '',
p.stores?.tempo ? tag('#005239', 'stit_tempo.png', 'Tempo') : ''
].join('');
// Biely okraj okolo nich ako skupiny: pod štítkami je biela vrstva z rovnakých štítkov, každý o hrúbku okraja
// väčší a so zaoblenými rohmi – kde sa štítky dotýkajú, biela splynie, takže linka ide okolo celej skupiny
// a vonkajšie rohy sú zaoblené (polovica hrúbky linky okolo pozadia cenovky: 2,25 px pri cenovke širokej 130 px).
function renderStores(p: CardProduct, mode: RenderMode, tagHeight: number, badgeWidth: number): string {
const t = Math.round(((2.25 * badgeWidth) / 130) * 10) / 10;
const tags = [
p.stores?.potraviny ? ['#e2001a', 'stit_potraviny.png', 'Potraviny'] : null,
p.stores?.supermarket ? ['#f9890e', 'stit_supermarket.png', 'Supermarket'] : null,
p.stores?.tempo ? ['#005239', 'stit_tempo.png', 'Tempo'] : null
].filter((x): x is string[] => !!x);
if (!tags.length) return '';
const row = ([color, file, alt]: string[], halo: boolean) =>
`<div style="text-align: right; line-height: 0;"><img src="${escapeHtml(assetUrl(SUPABASE_ASSETS_BASE + file, mode))}" height="${tagHeight}" alt="${halo ? '' : alt}" style="display: inline-block; height: ${tagHeight}px; width: auto; border: 0; ${
halo
? `background-color: #ffffff; filter: brightness(0) invert(1); padding: ${t}px; margin: -${t}px; border-radius: ${t}px; box-sizing: content-box;`
: `background-color: ${color};`
}"></div>`;
return `<div style="position: relative;">
<div style="position: absolute; inset: 0;" aria-hidden="true">${tags.map((x) => row(x, true)).join('')}</div>
<div style="position: relative;">${tags.map((x) => row(x, false)).join('')}</div>
</div>`;
}
// Jednoznačný kľúč vizuálu – pri zmene čohokoľvek, čo ovplyvní obrázok, sa pri exporte vytvorí nový
@@ -323,7 +328,7 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str
const oy = p.imageOffsetY || 0;
const sx = Math.round(p.storesOffsetX ?? DEFAULT_STORES_OFFSET_X);
const sy = Math.round(p.storesOffsetY ?? DEFAULT_STORES_OFFSET_Y);
const stores = renderStores(p, ctx.mode, p.storesSize ?? DEFAULT_STORES_SIZE);
const stores = renderStores(p, ctx.mode, p.storesSize ?? DEFAULT_STORES_SIZE, bw);
// 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;
@@ -341,7 +346,7 @@ function renderVisualPreview(p: CardProduct, ctx: CardContext, key: string): str
.join('')}
${(p.labels || []).map(renderLabel).join('')}
<div style="position: absolute; right: 8px; bottom: 8px; width: ${bw}px;">
${stores ? `<div style="position: absolute; right: ${insetRight - sx}px; bottom: calc(100% - ${insetTop}px + ${sy}px); ${storesOutline(bw)}">${stores}</div>` : ''}
${stores ? `<div style="position: absolute; right: ${insetRight - sx}px; bottom: calc(100% - ${insetTop}px + ${sy}px);">${stores}</div>` : ''}
${renderBadge(p, ctx, bw)}
</div>
</div>`;