// Google Opal AI - iOS Studio & 3D Poster Lab Engine document.addEventListener('DOMContentLoaded', () => { initNavigation(); initSettings(); initPosterLab(); initInboxAI(); initHistory(); }); // --- 1. Tab Navigation --- function initNavigation() { const navItems = document.querySelectorAll('.nav-item'); const tabPages = document.querySelectorAll('.tab-page'); navItems.forEach(item => { item.addEventListener('click', () => { const targetTabId = item.getAttribute('data-tab'); navItems.forEach(n => n.classList.remove('active')); tabPages.forEach(p => p.classList.remove('active')); item.classList.add('active'); const targetPage = document.getElementById(targetTabId); if (targetPage) targetPage.classList.add('active'); window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); // Language Toggle const langBtn = document.getElementById('langToggleBtn'); let currentLang = 'BN'; langBtn.addEventListener('click', () => { currentLang = currentLang === 'BN' ? 'EN' : 'BN'; langBtn.textContent = currentLang === 'BN' ? 'đ āĻŦāĻžāĻ' : 'đ EN'; }); } // Global App Switcher window.openAppRunner = function(appType) { if (appType === 'inbox_ai') { document.querySelector('.nav-item[data-tab="tabInboxAI"]').click(); } else { document.querySelector('.nav-item[data-tab="tabPosterLab"]').click(); } }; window.copyText = function(elementId) { const el = document.getElementById(elementId); if (el && el.textContent) { navigator.clipboard.writeText(el.textContent).then(() => { alert('â Copied to Clipboard!'); }); } }; // --- 2. Settings & Gemini/Gemma API --- function initSettings() { const apiKeyInput = document.getElementById('apiKeyInput'); const modelSelect = document.getElementById('modelSelect'); const n8nWebhookInput = document.getElementById('n8nWebhookInput'); const saveBtn = document.getElementById('saveApiKeyBtn'); const statusMsg = document.getElementById('settingsStatusMsg'); // Load saved settings const savedKey = localStorage.getItem('opal_gemini_api_key') || ''; const savedModel = localStorage.getItem('opal_selected_model') || 'gemini-3.6-flash'; const savedN8n = localStorage.getItem('opal_n8n_webhook') || 'https://n8n.auraajenticai.cloud/webhook/gt-auto-post'; apiKeyInput.value = savedKey; modelSelect.value = savedModel; if (n8nWebhookInput) n8nWebhookInput.value = savedN8n; saveBtn.addEventListener('click', async () => { const key = apiKeyInput.value.trim(); const model = modelSelect.value; const n8nUrl = n8nWebhookInput ? n8nWebhookInput.value.trim() : ''; if (n8nUrl) { localStorage.setItem('opal_n8n_webhook', n8nUrl); } if (!key) { alert('Settings saved!'); return; } statusMsg.classList.remove('hidden'); statusMsg.style.color = '#F472B6'; statusMsg.textContent = 'âŗ Testing AI connection with Google...'; try { const res = await callGeminiApi(key, model, "Respond with: 'Opal AI Connected successfully!'"); if (res) { localStorage.setItem('opal_gemini_api_key', key); localStorage.setItem('opal_selected_model', model); statusMsg.style.color = '#10B981'; statusMsg.textContent = `â Settings Saved & Verified: ${res.trim()}`; } } catch (e) { statusMsg.style.color = '#EF4444'; statusMsg.textContent = `â Error: ${e.message}`; } }); } async function callGeminiApi(apiKey, model, prompt) { const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 0.7, maxOutputTokens: 2048 } }; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { const errData = await response.json(); throw new Error(errData?.error?.message || `HTTP ${response.status}`); } const data = await response.json(); return data?.candidates?.[0]?.content?.parts?.[0]?.text || ''; } // --- 3. 3D Poster Lab Engine (True 3D Dual-Tier Gold Podium) --- function initPosterLab() { const productInput = document.getElementById('productInput'); const productPrice = document.getElementById('productPrice'); const photoFileInput = document.getElementById('photoFileInput'); const clearPhotoBtn = document.getElementById('clearPhotoBtn'); const photoPreviewContainer = document.getElementById('photoPreviewContainer'); const attachedPhotoImg = document.getElementById('attachedPhotoImg'); const generatePosterBtn = document.getElementById('generatePosterBtn'); const posterStatusText = document.getElementById('posterStatusText'); const posterResultCard = document.getElementById('posterResultCard'); const posterCanvas = document.getElementById('posterCanvas'); const downloadPosterBtn = document.getElementById('downloadPosterBtn'); const shareWhatsAppBtn = document.getElementById('shareWhatsAppBtn'); let attachedImageBitmap = null; // Photo Input change photoFileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { attachedPhotoImg.src = event.target.result; photoPreviewContainer.classList.remove('hidden'); clearPhotoBtn.classList.remove('hidden'); const img = new Image(); img.onload = () => { attachedImageBitmap = img; }; img.src = event.target.result; }; reader.readAsDataURL(file); } }); clearPhotoBtn.addEventListener('click', () => { photoFileInput.value = ''; attachedImageBitmap = null; photoPreviewContainer.classList.add('hidden'); clearPhotoBtn.classList.add('hidden'); }); // Generate 3D Poster generatePosterBtn.addEventListener('click', async () => { const inputVal = productInput.value.trim(); const priceVal = productPrice.value.trim(); posterStatusText.classList.remove('hidden'); posterStatusText.textContent = 'âŗ Resolving product image & rendering 3D gold stage...'; generatePosterBtn.disabled = true; try { let productImg = attachedImageBitmap; let title = "Authentic Korean K-Beauty Care"; if (!productImg) { // Universal Product / Image Link Resolver const resolved = await resolveProductUrl(inputVal); if (resolved && resolved.imageUrl) { title = resolved.title || inputVal; productImg = await loadImage(resolved.imageUrl); } else { // Fallback packshot image const fallbackUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(inputVal + " luxury cosmetic container jar bottle on podium 8k")}?width=1024&height=1024&nologo=true`; productImg = await loadImage(fallbackUrl); title = inputVal; } } if (productImg) { // Render True 3D Poster on Canvas await render3DCommercialPoster(posterCanvas, productImg, title, priceVal); posterResultCard.classList.remove('hidden'); posterStatusText.classList.add('hidden'); posterResultCard.scrollIntoView({ behavior: 'smooth' }); // Save to History saveGenerationToHistory({ type: '3D Poster', title: title, price: priceVal, date: new Date().toLocaleTimeString() }); } else { posterStatusText.textContent = 'â Failed to load product image. Please check link.'; } } catch (err) { posterStatusText.textContent = `â Error: ${err.message}`; } generatePosterBtn.disabled = false; }); // Download Button (Save to iPhone Camera Roll) downloadPosterBtn.addEventListener('click', () => { const link = document.createElement('a'); link.download = `GT_Poster_${Date.now()}.jpg`; link.href = posterCanvas.toDataURL('image/jpeg', 0.95); link.click(); }); // WhatsApp Order Button shareWhatsAppBtn.addEventListener('click', () => { const text = encodeURIComponent(`⨠100% Authentic Korean Skincare Order!\nProduct: ${productInput.value}\nPrice: ${productPrice.value}\nWebsite: https://glamourstouch.com`); window.open(`https://wa.me/8801712426871?text=${text}`, '_blank'); }); // 1-Click Auto Post to Facebook Page (via n8n Webhook) const autoPostN8nBtn = document.getElementById('autoPostN8nBtn'); const n8nPostStatusMsg = document.getElementById('n8nPostStatusMsg'); if (autoPostN8nBtn) { autoPostN8nBtn.addEventListener('click', async () => { const webhookUrl = localStorage.getItem('opal_n8n_webhook'); if (!webhookUrl) { alert('â ī¸ Please set your n8n Webhook URL in Settings first!'); document.querySelector('.nav-item[data-tab="tabSettings"]').click(); return; } n8nPostStatusMsg.classList.remove('hidden'); n8nPostStatusMsg.style.color = '#38BDF8'; n8nPostStatusMsg.textContent = 'âŗ Sending 3D poster & caption to n8n Webhook...'; autoPostN8nBtn.disabled = true; try { const posterDataUrl = posterCanvas.toDataURL('image/jpeg', 0.92); const payload = { title: productInput.value.trim() || 'Authentic Korean K-Beauty Skincare', price: productPrice.value.trim() || 'ā§ŗ ā§§,ā§Ļā§Ŧā§Ļ', caption: `⨠${productInput.value.trim()} â¨\n\nđ° āĻ āĻĢāĻžāϰ āĻŽā§āϞā§āϝ: ${productPrice.value.trim()}\nđĄī¸ ā§§ā§Ļā§Ļ% āĻ āϰāĻŋāĻāĻŋāύāĻžāϞ āĻā§āϰāĻŋā§āĻžāύ āĻāϏāĻŽā§āĻāĻŋāĻā§āϏ āĻā§āϝāĻžāϰāĻžāύā§āĻāĻŋ!\n\nđŦ āϏāϰāĻžāϏāϰāĻŋ WhatsApp āĻ āĻ āϰā§āĻĄāĻžāϰ āĻāϰāϤ⧠āĻāύāĻŦāĻā§āϏ āĻāϰā§āύ:\nđ https://wa.me/8801712426871\nđ āĻšāĻāϞāĻžāĻāύ: 01712-426871\nđ āĻā§ā§āĻŦāϏāĻžāĻāĻ: https://glamourstouch.com`, image_url: posterDataUrl, store: "Glamour's Touch", website: "https://glamourstouch.com", whatsapp: "01712-426871", timestamp: new Date().toISOString() }; const res = await fetch(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (res.ok) { const resData = await res.json().catch(() => ({})); n8nPostStatusMsg.style.color = '#10B981'; n8nPostStatusMsg.textContent = `â ${resData.message || 'đ āĻĒā§āϏā§āĻāĻāĻŋ āϏāĻĢāϞāĻāĻžāĻŦā§ n8n-āĻāϰ āĻŽāĻžāϧā§āϝāĻŽā§ Facebook Page-āĻ āĻĒāĻžāĻŦāϞāĻŋāĻļ āĻšā§ā§āĻā§!'}`; } else { n8nPostStatusMsg.style.color = '#EF4444'; n8nPostStatusMsg.textContent = `â n8n Webhook returned HTTP ${res.status}`; } } catch (err) { n8nPostStatusMsg.style.color = '#EF4444'; n8nPostStatusMsg.textContent = `â Connection Error: ${err.message}`; } autoPostN8nBtn.disabled = false; }); } } // Universal Link & Product Name Resolver async function resolveProductUrl(input) { if (!input) return null; if (input.match(/\.(jpeg|jpg|gif|png|webp)($|\?)/i)) { return { title: 'Authentic Korean Product', imageUrl: input }; } const key = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZtY2x0cmpudXZ1b29hcmt2dWZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzUxMzY3MDQsImV4cCI6MjA5MDcxMjcwNH0.PkSgBAZx41X4sZurfyOdxCVa01hkKTkyBhVkGzx_4y4'; // 1. Glamour's Touch Supabase UUID regex const uuidMatch = input.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/); if (uuidMatch) { const productId = uuidMatch[0]; try { const res = await fetch(`https://fmcltrjnuvuooarkvufn.supabase.co/rest/v1/products?id=eq.${productId}&select=*`, { headers: { 'apikey': key, 'Authorization': `Bearer ${key}` } }); const data = await res.json(); if (data && data.length > 0) { const p = data[0]; return { title: p.name || 'Authentic Korean Skincare', price: p.price ? 'ā§ŗ ' + p.price : '', imageUrl: p.image || p.image_url || (p.images && p.images[0]) || '' }; } } catch (e) { console.warn('GT Supabase fetch by ID failed:', e); } } // 2. Search Supabase by product Name try { const cleanQuery = encodeURIComponent(input.replace(/https?:\/\/[^\s]+/g, '').trim().slice(0, 30)); if (cleanQuery) { const res = await fetch(`https://fmcltrjnuvuooarkvufn.supabase.co/rest/v1/products?name=ilike.*${cleanQuery}*&select=*&limit=1`, { headers: { 'apikey': key, 'Authorization': `Bearer ${key}` } }); const data = await res.json(); if (data && data.length > 0) { const p = data[0]; return { title: p.name || input, price: p.price ? 'ā§ŗ ' + p.price : '', imageUrl: p.image || p.image_url || (p.images && p.images[0]) || '' }; } } } catch (e) { console.warn('GT Supabase search by name failed:', e); } // 3. Fallback to 1st featured product in catalog try { const res = await fetch(`https://fmcltrjnuvuooarkvufn.supabase.co/rest/v1/products?in_stock=eq.true&image=not.is.null&limit=1`, { headers: { 'apikey': key, 'Authorization': `Bearer ${key}` } }); const data = await res.json(); if (data && data.length > 0) { const p = data[0]; return { title: p.name || input, price: p.price ? 'ā§ŗ ' + p.price : '', imageUrl: p.image || '' }; } } catch (e) { console.warn('Fallback failed:', e); } return { title: input, imageUrl: 'https://fmcltrjnuvuooarkvufn.supabase.co/storage/v1/object/public/products/product-images/koba-74d48daa-7695-41f9-8f10-9e5fb4f0e2d9.jpg' }; } 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'; } 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 across CORS')); fallbackImg.src = src; }; img.src = loadUrl; }); } // --- 4. Masterpiece 3D Canvas Rendering Engine --- async function render3DCommercialPoster(canvas, productImg, title, price) { const ctx = canvas.getContext('2d'); const w = 1080; const h = 1080; // 1. Midnight Obsidian Background const bgGrad = ctx.createRadialGradient(w / 2, h * 0.44, 50, w / 2, h * 0.44, w * 0.72); bgGrad.addColorStop(0, '#18212D'); bgGrad.addColorStop(0.6, '#0C1117'); bgGrad.addColorStop(1, '#05080C'); ctx.fillStyle = bgGrad; ctx.fillRect(0, 0, w, h); // 2. White-to-Midnight Luxury Radial Spotlight (Smooth Studio Blend) const stageCx = w / 2; const stageCy = h * 0.44; const stageRadius = w * 0.38; const spotGrad = ctx.createRadialGradient(stageCx, stageCy, 0, stageCx, stageCy, stageRadius); spotGrad.addColorStop(0, '#FFFFFF'); spotGrad.addColorStop(0.46, '#FFFFFF'); spotGrad.addColorStop(0.64, '#F0F5FA'); spotGrad.addColorStop(0.82, '#8E9FAC'); spotGrad.addColorStop(0.94, '#283545'); spotGrad.addColorStop(1, '#0C1117'); ctx.fillStyle = spotGrad; ctx.beginPath(); ctx.arc(stageCx, stageCy, stageRadius, 0, Math.PI * 2); ctx.fill(); // Luxury Gold Dashed Rings ctx.strokeStyle = '#66E5B83A'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.arc(stageCx, stageCy, stageRadius * 0.96, 0, Math.PI * 2); ctx.stroke(); ctx.strokeStyle = '#99E5B83A'; ctx.lineWidth = 1.8; ctx.setLineDash([12, 12]); ctx.beginPath(); ctx.arc(stageCx, stageCy, stageRadius * 0.91, 0, Math.PI * 2); ctx.stroke(); ctx.setLineDash([]); // 3. True 3D Dual-Tier Cylindrical Podium // --- TIER 1: Lower Base --- const baseTierY = 745; const baseTierRx = w * 0.38; const baseTierRy = 34; const baseTierHeight = 26; // Tier 1 Ground Shadow const baseShadow = ctx.createRadialGradient(stageCx, baseTierY + baseTierHeight + 16, 20, stageCx, baseTierY + baseTierHeight + 16, baseTierRx * 1.15); baseShadow.addColorStop(0, 'rgba(0,0,0,0.7)'); baseShadow.addColorStop(1, 'transparent'); ctx.fillStyle = baseShadow; drawOval(ctx, stageCx, baseTierY + baseTierHeight + baseTierRy * 0.6, baseTierRx * 1.15, baseTierRy * 1.6); // Tier 1 Cylinder Body const baseCylGrad = ctx.createLinearGradient(stageCx - baseTierRx, 0, stageCx + baseTierRx, 0); baseCylGrad.addColorStop(0, '#090D12'); baseCylGrad.addColorStop(0.2, '#1C2633'); baseCylGrad.addColorStop(0.5, '#6C829E'); baseCylGrad.addColorStop(0.8, '#1C2633'); baseCylGrad.addColorStop(1, '#090D12'); ctx.fillStyle = baseCylGrad; ctx.fillRect(stageCx - baseTierRx, baseTierY, baseTierRx * 2, baseTierHeight); drawOval(ctx, stageCx, baseTierY + baseTierHeight, baseTierRx, baseTierRy); // Tier 1 Top Cap const baseTopGrad = ctx.createLinearGradient(stageCx - baseTierRx, baseTierY - baseTierRy, stageCx + baseTierRx, baseTierY + baseTierRy); baseTopGrad.addColorStop(0, '#222D3A'); baseTopGrad.addColorStop(1, '#0B1017'); ctx.fillStyle = baseTopGrad; drawOval(ctx, stageCx, baseTierY, baseTierRx, baseTierRy); // --- TIER 2: Upper Product Pedestal --- const topTierY = 708; const topTierRx = w * 0.31; const topTierRy = 28; const topTierHeight = 34; // Tier 2 Cylinder Body const topCylGrad = ctx.createLinearGradient(stageCx - topTierRx, 0, stageCx + topTierRx, 0); topCylGrad.addColorStop(0, '#070A0E'); topCylGrad.addColorStop(0.25, '#18212C'); topCylGrad.addColorStop(0.5, '#F5E6AB'); topCylGrad.addColorStop(0.75, '#18212C'); topCylGrad.addColorStop(1, '#070A0E'); ctx.fillStyle = topCylGrad; ctx.fillRect(stageCx - topTierRx, topTierY, topTierRx * 2, topTierHeight); drawOval(ctx, stageCx, topTierY + topTierHeight, topTierRx, topTierRy); // Tier 2 Top Surface (Glossy White/Platinum Cap) const topSurfaceGrad = ctx.createRadialGradient(stageCx, topTierY, 0, stageCx, topTierY, topTierRx); topSurfaceGrad.addColorStop(0, '#FFFFFF'); topSurfaceGrad.addColorStop(0.45, '#EBF2F8'); topSurfaceGrad.addColorStop(0.8, '#899EAF'); topSurfaceGrad.addColorStop(1, '#1B2533'); ctx.fillStyle = topSurfaceGrad; drawOval(ctx, stageCx, topTierY, topTierRx, topTierRy); // Tier 2 Top Gold Beveled Rim const goldRimGrad = ctx.createLinearGradient(stageCx - topTierRx, topTierY, stageCx + topTierRx, topTierY); goldRimGrad.addColorStop(0, '#B38728'); goldRimGrad.addColorStop(0.5, '#FFFFFF'); goldRimGrad.addColorStop(1, '#AA771C'); ctx.strokeStyle = goldRimGrad; ctx.lineWidth = 3.5; strokeOval(ctx, stageCx, topTierY, topTierRx, topTierRy); // 4. Hero Product Scaling & Placement const targetH = 500; const targetW = 520; const srcW = productImg.width || 500; const srcH = productImg.height || 500; const scale = Math.min(targetW / srcW, targetH / srcH); const destW = srcW * scale; const destH = srcH * scale; const destLeft = (w - destW) / 2; const destTop = topTierY - destH + 18; // 4.1 Glossy Mirror Product Reflection (Multiply blended) ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.globalAlpha = 0.25; ctx.translate(0, (topTierY + 6) * 2); ctx.scale(1, -1); ctx.drawImage(productImg, destLeft, topTierY + 6, destW, destH * 0.35); ctx.restore(); // Contact Drop Shadow const contactShadow = ctx.createRadialGradient(stageCx, topTierY, 10, stageCx, topTierY, destW * 0.4); contactShadow.addColorStop(0, 'rgba(0,0,0,0.8)'); contactShadow.addColorStop(1, 'transparent'); ctx.fillStyle = contactShadow; drawOval(ctx, stageCx, topTierY + 2, destW * 0.38, 12); // Draw Hero Product with Multiply Blend Mode (removes white background completely) ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(productImg, destLeft, destTop, destW, destH); ctx.restore(); // 5. Value Trust Badges drawPillBadge(ctx, 40, 240, 'đ°đˇ 100% Original Korea', true); drawPillBadge(ctx, 40, 315, 'đŋ Skin Friendly Formula', true); drawPillBadge(ctx, w - 40, 240, '⥠Fast Delivery BD', false); drawPillBadge(ctx, w - 40, 315, 'đĄī¸ Authentic Guarantee', false); // 6. Top Header Ribbon ctx.fillStyle = '#F2090D12'; ctx.fillRect(0, 0, w, 92); ctx.strokeStyle = '#E5B83A'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(0, 92); ctx.lineTo(w, 92); ctx.stroke(); ctx.fillStyle = '#E5B83A'; ctx.font = 'bold 34px Plus Jakarta Sans, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('⨠GLAMOUR\'S TOUCH â¨', w / 2, 52); ctx.fillStyle = '#FFF4D0'; ctx.font = '17px Plus Jakarta Sans, sans-serif'; ctx.fillText('ëíë¯ŧęĩ íëĻŦ미ė ė¤í¨ėŧė´ âĸ 100% AUTHENTIC KOREAN COSMETICS', w / 2, 80); // 7. Product Title Card const cleanTitle = title.length > 60 ? title.substring(0, 58) + '...' : title; const titleCardTop = h - 235; const titleCardBottom = h - 128; ctx.fillStyle = '#EA0B0E14'; roundRect(ctx, 28, titleCardTop, w - 56, titleCardBottom - titleCardTop, 18); ctx.fill(); ctx.strokeStyle = '#55E5B83A'; ctx.lineWidth = 2; roundRect(ctx, 28, titleCardTop, w - 56, titleCardBottom - titleCardTop, 18); ctx.stroke(); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 27px Plus Jakarta Sans, Hind Siliguri, sans-serif'; ctx.textAlign = 'center'; ctx.fillText(cleanTitle, w / 2, titleCardTop + 42); ctx.fillStyle = '#E5B83A'; ctx.font = 'bold 20px Hind Siliguri, sans-serif'; ctx.fillText('āĻā§āϞāĻžāϏ āϏā§āĻāĻŋāύ āĻ āĻĻāĻžāĻāĻšā§āύ āĻāĻā§āĻā§āĻŦāϞāϤāĻžāϰ āĻā§āϰāĻŋā§āĻžāύ āϏāĻŋāĻā§āϰā§āĻ âĸ āϏāϰāĻžāϏāϰāĻŋ āĻā§āϰāĻŋā§āĻž āĻĨā§āĻā§ āĻāĻŽāĻĻāĻžāύāĻŋāĻā§āϤ', w / 2, titleCardTop + 78); // 8. Bottom WhatsApp Order Ribbon (Green Banner) const footerTop = h - 118; const footerGrad = ctx.createLinearGradient(0, footerTop, w, h); footerGrad.addColorStop(0, '#25D366'); footerGrad.addColorStop(1, '#1EB855'); ctx.fillStyle = footerGrad; ctx.fillRect(0, footerTop, w, 118); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 32px Plus Jakarta Sans, sans-serif'; ctx.textAlign = 'left'; ctx.fillText('đŦ WhatsApp: 01712-426871', 35, footerTop + 48); ctx.fillStyle = '#053314'; ctx.font = 'bold 21px Plus Jakarta Sans, sans-serif'; ctx.fillText('đ Website: glamourstouch.com âĸ đ Call: 01712-426871', 35, footerTop + 90); // 8.1 Render Scannable QR Code on Bottom Right await drawWhatsAppQrOnCanvas(ctx, w - 130, footerTop + 11, 96); // 9. Outer Gold Frame Edge ctx.strokeStyle = '#88E5B83A'; ctx.lineWidth = 6; ctx.strokeRect(3, 3, w - 6, h - 6); } function drawOval(ctx, cx, cy, rx, ry) { ctx.beginPath(); ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); ctx.fill(); } function strokeOval(ctx, cx, cy, rx, ry) { ctx.beginPath(); ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); ctx.stroke(); } function roundRect(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } function drawPillBadge(ctx, x, y, text, isLeft) { ctx.font = 'bold 18px Plus Jakarta Sans, Hind Siliguri, sans-serif'; const textWidth = ctx.measureText(text).width; const pillW = textWidth + 28; const pillH = 36; const pillX = isLeft ? x : x - pillW; const pillY = y - 22; ctx.fillStyle = '#D90E141B'; roundRect(ctx, pillX, pillY, pillW, pillH, 18); ctx.fill(); ctx.strokeStyle = '#55E5B83A'; ctx.lineWidth = 1.5; roundRect(ctx, pillX, pillY, pillW, pillH, 18); ctx.stroke(); ctx.fillStyle = '#FFF0C8'; ctx.textAlign = 'left'; ctx.fillText(text, pillX + 14, pillY + 24); } async function drawWhatsAppQrOnCanvas(ctx, x, y, size) { // White background container ctx.fillStyle = '#FFFFFF'; roundRect(ctx, x - 4, y - 4, size + 8, size + 8, 10); ctx.fill(); // Create temporary QR element const qrContainer = document.getElementById('qrcodeHidden'); qrContainer.innerHTML = ''; new QRCode(qrContainer, { text: 'https://wa.me/8801712426871', width: size, height: size, colorDark: '#0B2E13', colorLight: '#FFFFFF', correctLevel: QRCode.CorrectLevel.M }); await new Promise(r => setTimeout(r, 80)); const qrImg = qrContainer.querySelector('img') || qrContainer.querySelector('canvas'); if (qrImg) { ctx.drawImage(qrImg, x, y, size, size); } } // --- 5. Customer Care & Inbox AI --- function initInboxAI() { const customerMsgInput = document.getElementById('inboxCustomerMsg'); const storeContextInput = document.getElementById('inboxStoreContext'); const generateBtn = document.getElementById('generateInboxReplyBtn'); const statusMsg = document.getElementById('inboxStatusMsg'); const repliesCard = document.getElementById('inboxRepliesCard'); const reply1 = document.getElementById('replyText1'); const reply2 = document.getElementById('replyText2'); const reply3 = document.getElementById('replyText3'); generateBtn.addEventListener('click', async () => { const query = customerMsgInput.value.trim(); const storeInfo = storeContextInput.value.trim(); const apiKey = localStorage.getItem('opal_gemini_api_key') || ''; const model = localStorage.getItem('opal_selected_model') || 'gemini-3.6-flash'; if (!query) { alert('Please enter a customer message'); return; } statusMsg.classList.remove('hidden'); statusMsg.textContent = 'âŗ Generating 3 sweet & polite sales replies with Gemma AI...'; generateBtn.disabled = true; try { if (apiKey) { const prompt = `A customer sent this message in Glamour's Touch inbox: '${query}' Store Info: ${storeInfo} Write 3 ready-to-copy customer responses in natural, sweet BENGALI (āĻŦāĻžāĻāϞāĻž). Format exactly with: ---REPLY1--- (Instant polite greeting and direct answer with price & benefits) ---REPLY2--- (Order confirmation closer asking for Name, Address, and Phone number) ---REPLY3--- (1-click WhatsApp order link: https://wa.me/8801712426871 and hotline 01712-426871)`; const result = await callGeminiApi(apiKey, model, prompt); const parts = result.split(/---REPLY[123]---/); reply1.textContent = (parts[1] || 'āĻāϏāϏāĻžāϞāĻžāĻŽā§ āĻāϞāĻžāĻāĻā§āĻŽ āĻĒā§āϰāĻŋā§ āĻāĻžāϏā§āĻāĻŽāĻžāϰ! āĻāĻŽāĻžāĻĻā§āϰ āĻā§āϞāĻžāϏ āϏā§āĻāĻŋāύ āϏāĻŋāϰāĻžāĻŽā§āϰ āĻĻāĻžāĻŽ āĻŽāĻžāϤā§āϰ ā§ŗā§§ā§Ŧā§Ļā§ĻāĨ¤ āĻāĻāĻŋ ā§§ā§Ļā§Ļ% āĻāϏāϞ āĻā§āϰāĻŋā§āĻžāύ āĻĒā§āϰāĻĄāĻžāĻā§āĻāĨ¤').trim(); reply2.textContent = (parts[2] || 'āĻ āϰā§āĻĄāĻžāϰāĻāĻŋ āĻāύāĻĢāĻžāϰā§āĻŽ āĻāϰāϤ⧠āĻāĻĒāύāĻžāϰ āύāĻžāĻŽ, āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻ āĻŋāĻāĻžāύāĻž āĻāĻŦāĻ āĻŽā§āĻŦāĻžāĻāϞ āύāĻŽā§āĻŦāϰāĻāĻŋ āϞāĻŋāĻā§ āĻĒāĻžāĻ āĻžāύāĨ¤ āĻāĻŽāϰāĻž āĻĻā§āϰā§āϤ āĻā§āϝāĻžāĻļ āĻ āύ āĻĄā§āϞāĻŋāĻāĻžāϰāĻŋāϤ⧠āĻĒāĻžāĻ āĻŋā§ā§ āĻĻā§āĻŦā§!').trim(); reply3.textContent = (parts[3] || 'āϏāϰāĻžāϏāϰāĻŋ āĻšā§ā§āĻžāĻāϏāĻ ā§āϝāĻžāĻĒā§ āĻĻā§āϰā§āϤ āĻ āϰā§āĻĄāĻžāϰ āĻāϰāϤ⧠āĻā§āϞāĻŋāĻ āĻāϰā§āύ: https://wa.me/8801712426871\nāĻšāĻāϞāĻžāĻāύ: 01712-426871').trim(); } else { // High quality offline fallback template reply1.textContent = `āĻāϏāϏāĻžāϞāĻžāĻŽā§ āĻāϞāĻžāĻāĻā§āĻŽ āĻĒā§āϰāĻŋā§ āĻāĻĒā§/āĻāĻžāĻā§āĻž! đĨ°\nāĻāĻŽāĻžāĻĻā§āϰ āĻāĻžāĻā§ ā§§ā§Ļā§Ļ% āĻ āϰāĻŋāĻāĻŋāύāĻžāϞ āĻā§āϰāĻŋā§āĻžāύ āϏā§āĻāĻŋāύāĻā§ā§āĻžāϰ āĻĒā§āϰā§āĻĄāĻžāĻā§āĻ āϰā§ā§āĻā§āĨ¤\n\nđ° āĻ āĻĢāĻžāϰ āĻŽā§āϞā§āϝ: ā§ŗ ā§§,ā§Ļā§Ŧā§Ļ (āϰā§āĻā§āϞāĻžāϰ ā§ŗ ā§§,ā§Ēā§ā§Ļ)\nđ āĻšā§āĻŽ āĻĄā§āϞāĻŋāĻāĻžāϰāĻŋ: āĻĸāĻžāĻāĻžā§ ā§ŗā§ā§Ļ, āĻĸāĻžāĻāĻžāϰ āĻŦāĻžāĻāĻ°ā§ ā§ŗā§§ā§Šā§Ļ\n\nāĻāĻāĻŋ āύāĻŋā§āĻŽāĻŋāϤ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧠āϤā§āĻŦāĻ āĻšāĻŦā§ āĻāĻžāĻā§āϰ āĻŽāϤ⧠āĻāĻāĻāĻā§ āĻ āĻĻāĻžāĻāĻšā§āύ! đ¸`; reply2.textContent = `āĻ āϰā§āĻĄāĻžāϰāĻāĻŋ āĻāύāĻĢāĻžāϰā§āĻŽ āĻāϰāϤ⧠āĻ āύā§āĻā§āϰāĻš āĻāϰ⧠āĻāĻĒāύāĻžāϰ:\nā§§. āύāĻžāĻŽ\n⧍. āϏāĻŽā§āĻĒā§āϰā§āĻŖ āĻ āĻŋāĻāĻžāύāĻž (āĻā§āϞāĻž/āĻĨāĻžāύāĻž āϏāĻš)\nā§Š. āĻŽā§āĻŦāĻžāĻāϞ āύāĻŽā§āĻŦāϰ\n\nāϞāĻŋāĻā§ āĻĒāĻžāĻ āĻŋā§ā§ āĻĻāĻŋāύāĨ¤ āĻāĻŽāϰāĻž āĻĻā§āϰā§āϤ āĻĒā§āϰā§āĻĄāĻžāĻā§āĻ āĻĒāĻžāĻ āĻŋā§ā§ āĻĻā§āĻŦā§! āϧāύā§āϝāĻŦāĻžāĻĻāĨ¤ đī¸`; reply3.textContent = `āϏāϰāĻžāϏāϰāĻŋ āĻšā§ā§āĻžāĻāϏāĻ ā§āϝāĻžāĻĒā§ ā§§-āĻā§āϞāĻŋāĻā§ āĻ āϰā§āĻĄāĻžāϰ āĻāϰāϤ⧠āĻāĻŽāĻžāĻĻā§āϰ āĻāύāĻŦāĻā§āϏ āĻāϰā§āύ:\nđŦ WhatsApp: https://wa.me/8801712426871\nđ āĻšāĻāϞāĻžāĻāύ: 01712-426871\nđ āĻā§ā§āĻŦāϏāĻžāĻāĻ: https://glamourstouch.com`; } repliesCard.classList.remove('hidden'); statusMsg.classList.add('hidden'); repliesCard.scrollIntoView({ behavior: 'smooth' }); } catch (e) { statusMsg.textContent = `â Error: ${e.message}`; } generateBtn.disabled = false; }); } // --- 6. History Management --- function initHistory() { const historyContainer = document.getElementById('historyListContainer'); const clearBtn = document.getElementById('clearHistoryBtn'); function renderHistory() { const list = JSON.parse(localStorage.getItem('opal_history') || '[]'); if (list.length === 0) { historyContainer.innerHTML = `
No generations yet. Create your first 3D poster or copy!
${item.price || item.type}