feat(poster): fix 3d poster bugs and add 1-click google trending search loader

This commit is contained in:
khondokartowsif171
2026-09-04 18:01:01 +06:00
parent 856cd47b72
commit a5fbabde2e
3 changed files with 173 additions and 35 deletions
+113 -34
View File
@@ -736,6 +736,25 @@ function initPosterLab() {
});
});
// Trending Google Search Chips (1-Click Auto Fill)
const trendChips = document.querySelectorAll('#trendingPosterChips .trend-chip');
trendChips.forEach(chip => {
chip.addEventListener('click', () => {
trendChips.forEach(c => c.classList.remove('active'));
chip.classList.add('active');
const brand = chip.getAttribute('data-brand');
const title = chip.getAttribute('data-title');
const price = chip.getAttribute('data-price');
const badge = chip.getAttribute('data-badge');
if (brand && brandWatermarkSelect) brandWatermarkSelect.value = brand;
if (title && productInput) productInput.value = title;
if (price && productPrice) productPrice.value = price;
if (badge && customBadgeInput) customBadgeInput.value = badge;
});
});
let attachedImageBitmap = null;
// Photo Input change
@@ -947,26 +966,56 @@ async function resolveProductUrl(input) {
}
function loadImage(src) {
return new Promise((resolve, reject) => {
let loadUrl = src;
if (src.startsWith('http://') || src.startsWith('https://')) {
loadUrl = 'https://images.weserv.nl/?url=' + encodeURIComponent(src) + '&output=jpg&q=95';
return new Promise((resolve) => {
if (!src) {
const placeholder = createFallbackProductCanvas('Product Spotlight');
return resolve(placeholder);
}
if (src.startsWith('data:')) {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => resolve(createFallbackProductCanvas('Product'));
img.src = src;
return;
}
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(img);
img.onerror = () => {
const fallbackImg = new Image();
fallbackImg.crossOrigin = 'anonymous';
fallbackImg.onload = () => resolve(fallbackImg);
fallbackImg.onerror = () => reject(new Error('Could not load image'));
fallbackImg.src = src;
// Try proxy
const proxyImg = new Image();
proxyImg.crossOrigin = 'anonymous';
proxyImg.onload = () => resolve(proxyImg);
proxyImg.onerror = () => {
// Fallback to high quality generated canvas graphic
resolve(createFallbackProductCanvas('Premium Product'));
};
proxyImg.src = 'https://images.weserv.nl/?url=' + encodeURIComponent(src) + '&output=jpg&q=95';
};
img.src = loadUrl;
img.src = src;
});
}
function createFallbackProductCanvas(label = 'Product') {
const c = document.createElement('canvas');
c.width = 600;
c.height = 600;
const ctx = c.getContext('2d');
const g = ctx.createLinearGradient(0, 0, 600, 600);
g.addColorStop(0, '#38BDF8');
g.addColorStop(1, '#8B5CF6');
ctx.fillStyle = g;
ctx.roundRect ? ctx.roundRect(80, 80, 440, 440, 40) : ctx.fillRect(80, 80, 440, 440);
ctx.fill();
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 36px Plus Jakarta Sans, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('✨ ' + label, 300, 310);
return c;
}
// --- 3D Canvas Multi-Theme Rendering Engine ---
async function render3DCommercialPoster(canvas, productImg, title, price, badgeText, theme = 'obsidian_gold', aspect = '1:1', brand = 'glamourstouch') {
const ctx = canvas.getContext('2d');
@@ -1184,9 +1233,9 @@ async function render3DCommercialPoster(canvas, productImg, title, price, badgeT
ctx.fillStyle = contactShadow;
drawOval(ctx, stageCx, topTierY + 2, destW * 0.38, 12);
// Product Image (Multiply blended to wipe white background)
// Product Image (Crisp Solid Blend)
ctx.save();
ctx.globalCompositeOperation = 'multiply';
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(productImg, destLeft, destTop, destW, destH);
ctx.restore();
@@ -1218,8 +1267,7 @@ async function render3DCommercialPoster(canvas, productImg, title, price, badgeT
ctx.font = '16px Plus Jakarta Sans, sans-serif';
ctx.fillText(brandInfo.headerSub, w / 2, 80);
// 7. Product Title Card
const cleanTitle = title.length > 60 ? title.substring(0, 58) + '...' : title;
// 7. Product Title Card (Smart Multi-line wrap)
const titleCardTop = h - (aspect === '9:16' ? 320 : 235);
const titleCardBottom = h - (aspect === '9:16' ? 150 : 128);
@@ -1233,13 +1281,25 @@ async function render3DCommercialPoster(canvas, productImg, title, price, badgeT
ctx.stroke();
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 27px Plus Jakarta Sans, Hind Siliguri, sans-serif';
ctx.font = 'bold 26px Plus Jakarta Sans, Hind Siliguri, sans-serif';
ctx.textAlign = 'center';
ctx.fillText(cleanTitle, w / 2, titleCardTop + 42);
// Smart single/double line title drawing
const titleWords = title.split(' ');
if (titleWords.length > 5 && title.length > 35) {
const mid = Math.ceil(titleWords.length / 2);
const line1 = titleWords.slice(0, mid).join(' ');
const line2 = titleWords.slice(mid).join(' ');
ctx.fillText(line1, w / 2, titleCardTop + 34);
ctx.fillText(line2, w / 2, titleCardTop + 62);
} else {
const cleanTitle = title.length > 55 ? title.substring(0, 52) + '...' : title;
ctx.fillText(cleanTitle, w / 2, titleCardTop + 42);
}
ctx.fillStyle = currentTheme.goldRim;
ctx.font = 'bold 21px Hind Siliguri, sans-serif';
ctx.fillText(`${price} • ১০০% আসল গ্যারান্টি • দ্রুত হোম ডেলিভারি`, w / 2, titleCardTop + 80);
ctx.fillText(`${price} • ১০০% আসল গ্যারান্টি • দ্রুত হোম ডেলিভারি`, w / 2, titleCardTop + 88);
// 8. Bottom WhatsApp Banner
const footerHeight = aspect === '9:16' ? 140 : 118;
@@ -1260,7 +1320,7 @@ async function render3DCommercialPoster(canvas, productImg, title, price, badgeT
ctx.fillText(`🌐 Website: ${brandInfo.footerSite} • 📞 Hotline: ${brandInfo.footerHotline}`, 35, footerTop + 90);
// QR Code
await drawWhatsAppQrOnCanvas(ctx, w - 130, footerTop + 11, 96);
await drawWhatsAppQrOnCanvas(ctx, w - 130, footerTop + 11, 96, brandInfo.footerHotline);
// Outer Border
ctx.strokeStyle = currentTheme.goldRim + '99';
@@ -1316,29 +1376,48 @@ function drawPillBadge(ctx, x, y, text, isLeft, goldColor = '#E5B83A') {
ctx.fillText(text, pillX + 14, pillY + 24);
}
async function drawWhatsAppQrOnCanvas(ctx, x, y, size) {
async function drawWhatsAppQrOnCanvas(ctx, x, y, size, hotline = '01712-426871') {
ctx.fillStyle = '#FFFFFF';
roundRect(ctx, x - 4, y - 4, size + 8, size + 8, 10);
ctx.fill();
const qrContainer = document.getElementById('qrcodeHidden');
if (qrContainer) {
qrContainer.innerHTML = '';
new QRCode(qrContainer, {
text: 'https://wa.me/8801712426871',
width: size,
height: size,
colorDark: '#0B2E13',
colorLight: '#FFFFFF',
correctLevel: QRCode.CorrectLevel.M
});
const rawPhone = hotline.replace(/\D/g, '');
const waUrl = `https://wa.me/88${rawPhone.startsWith('88') ? rawPhone.slice(2) : rawPhone.replace(/^0/, '')}`;
await new Promise(r => setTimeout(r, 80));
const qrImg = qrContainer.querySelector('img') || qrContainer.querySelector('canvas');
if (qrImg) {
ctx.drawImage(qrImg, x, y, size, size);
const qrContainer = document.getElementById('qrcodeHidden');
if (qrContainer && typeof QRCode !== 'undefined') {
try {
qrContainer.innerHTML = '';
new QRCode(qrContainer, {
text: waUrl,
width: size,
height: size,
colorDark: '#0B2E13',
colorLight: '#FFFFFF',
correctLevel: QRCode.CorrectLevel.M
});
await new Promise(r => setTimeout(r, 60));
const qrImg = qrContainer.querySelector('img') || qrContainer.querySelector('canvas');
if (qrImg) {
ctx.drawImage(qrImg, x, y, size, size);
return;
}
} catch (e) {
console.warn('QRCode render exception:', e);
}
}
// Resilient High-Contrast Vector QR Fallback
ctx.fillStyle = '#0B2E13';
ctx.fillRect(x + 4, y + 4, size - 8, size - 8);
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(x + 10, y + 10, size - 20, size - 20);
ctx.fillStyle = '#0B2E13';
ctx.font = 'bold 13px Plus Jakarta Sans, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('SCAN', x + size / 2, y + size / 2 - 2);
ctx.fillText('ORDER', x + size / 2, y + size / 2 + 14);
}
// --- Customer Care & Inbox AI Dedicated Studio ---