// admin-script.js jQuery(document).ready(function($) { // Event delegation for buttons $(document).on('click', '.download-image-btn', function() { const canvas = document.getElementById($(this).data('canvas-id')); if (canvas && canvas.toBlob) { canvas.toBlob(blob => saveAs(blob, `${$(this).data('filename')}.png`), 'image/png'); } else { console.error('Canvas no válido o toBlob no soportado'); } }); $(document).on('click', '.regenerate-image-btn', function() { const canvas = document.getElementById($(this).data('canvas-id')); if (canvas) { generateSocialImage(canvas); } }); $(document).on('click', '.debug-options-btn', function() { const canvas = document.getElementById($(this).data('canvas-id')); if (!canvas) { console.error('Canvas no encontrado'); return; } console.group('Debug Opciones'); console.log('Canvas ID:', canvas.id); console.log('Datos:', canvas.dataset); console.log('Estilo:', canvas.dataset.designStyle || 'style1'); console.log('Imagen URL:', canvas.dataset.imageUrl || 'No definida'); console.log('Título:', canvas.dataset.postTitle || 'No definido'); console.log('Categoría:', canvas.dataset.postCategory || 'No definida'); console.log('Formato:', canvas.dataset.format || 'No definido'); if (canvas.dataset.imageUrl) { const testImg = new Image(); testImg.crossOrigin = 'anonymous'; testImg.onload = () => console.log('✅ Imagen cargable:', canvas.dataset.imageUrl); testImg.onerror = () => console.log('❌ Error cargando imagen:', canvas.dataset.imageUrl); testImg.src = canvas.dataset.imageUrl + '?v=' + new Date().getTime(); // Evitar caché } else { console.log('❌ No hay URL de imagen'); } console.groupEnd(); }); }); window.generateSocialImage = function(canvas) { if (!canvas || !canvas.getContext || !canvas.dataset) { console.error('Canvas no válido o sin datos'); return; } console.log('generateSocialImage llamado para canvas ID:', canvas.id); const ctx = canvas.getContext('2d'); const d = canvas.dataset; // Configuración con valores seguros const config = { title: d.postTitle || 'Sin título', category: d.postCategory || 'NEWS', excerpt: d.postExcerpt || '', imageUrl: d.imageUrl || '', websiteUrl: d.websiteUrl || 'www.esteponanoticias.com', style: d.designStyle || 'style1', format: d.format || 'story', titleColor: d.titleTextColor || '#FFFFFF', titleSize: parseInt(d.titleFontSize, 10) || (d.format === 'story' ? 48 : 28), catSize: parseInt(d.categoryFontSize, 10) || (d.format === 'story' ? 18 : 12), webSize: parseInt(d.websiteFontSize, 10) || (d.format === 'story' ? 16 : 12), titleTop: parseInt(d.titlePositionTop, 10) || (d.format === 'story' ? 200 : 100), catTop: parseInt(d.categoryPositionTop, 10) || (d.format === 'story' ? 60 : 25), webTop: parseInt(d.websitePositionTop, 10) || (d.format === 'story' ? 120 : 65), leftMargin: parseInt(d.titlePositionLeft, 10) || (d.format === 'story' ? 60 : 35), showCat: d.showCategory === '1', showWeb: d.showWebsite === '1', showTitle: d.showTitle === '1', logoUrl: d.logoUrl || '', showLogo: d.showLogo === '1', logoSize: parseInt(d.logoSize, 10) || (d.format === 'story' ? 120 : 80), logoTop: parseInt(d.logoPositionTop, 10) || (d.format === 'story' ? 30 : 15), logoLeft: parseInt(d.logoPositionLeft, 10) || (d.format === 'story' ? 30 : 15), filter: d.blackFilterEnabled === '1', filterOpacity: (parseFloat(d.blackFilterOpacity) || 40) / 100, lineColor: d.lineColor || '#FCD34D', lineWidth: parseInt(d.lineWidth, 10) || 8, lineMarginTop: parseInt(d.lineMarginTop, 10) || (d.format === 'story' ? 80 : 40), lineMarginBottom: parseInt(d.lineMarginBottom, 10) || (d.format === 'story' ? 80 : 40), lineMarginLeft: parseInt(d.lineMarginLeft, 10) || (d.format === 'story' ? 60 : 35), gradColor1: d.gradientColor1 || '#000000', gradColor2: d.gradientColor2 || '#000000', gradOpacity: (parseFloat(d.gradientOpacity) || 70) / 100, minShadowColor: d.minimalistShadowColor || '#000000', minShadowBlur: parseInt(d.minimalistShadowBlur, 10) || 10, minSpacing: parseInt(d.minimalistSpacing, 10) || 20, sideWidth: parseInt(d.sidebarWidth, 10) || 40, sideColor: d.sidebarColor || '#DC143C', sideText: d.sidebarText || 'BREAKING NEWS', sideFontSize: parseInt(d.sidebarFontSize, 10) || 36, sidebarFontFamily: d.sidebarFontFamily || 'Arial', blockMainColor: d.blockMainColor || '#E91E63', blockSubColor: d.blockSubColor || '#000000', centralGradColor: d.centralGradientColor || '#000000', buttonColor: d.buttonColor || '#FF6B35', backgroundColor: d.backgroundColor || '#FFFFFF', siteName: d.siteName || 'Breaking News', dmarbellaTextSize: parseInt(d.dmarbellaTextSize, 10) || 20, bannerColor: d.bannerColor || '#1E3A8A', bannerTextSize: parseInt(d.bannerTextSize, 10) || 24, zoom: parseFloat(d.zoomLevel) || 1.0, zoomX: parseFloat(d.zoomX) || 0, zoomY: parseFloat(d.zoomY) || 0 }; // Dibujar fondo temporal para depuración ctx.fillStyle = '#f0f0f0'; // Fondo gris claro para ver si se renderiza algo ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#000000'; ctx.font = '20px Arial'; ctx.fillText('Canvas inicializado - Estilo: ' + config.style, 50, 50); ctx.fillText('Título: ' + config.title, 50, 80); ctx.fillText('Categoría: ' + config.category, 50, 110); ctx.fillText('URL Imagen: ' + config.imageUrl, 50, 140); // Helpers const hexToRgba = (hex, alpha = 1) => { if (!hex || typeof hex !== 'string') return 'rgba(0,0,0,' + alpha + ')'; const cleanHex = hex.replace('#', ''); const r = parseInt(cleanHex.slice(0, 2), 16) || 0; const g = parseInt(cleanHex.slice(2, 4), 16) || 0; const b = parseInt(cleanHex.slice(4, 6), 16) || 0; return `rgba(${r},${g},${b},${alpha})`; }; const wrapText = (text, x, y, maxWidth, fontSize, color, isBold = false, lineHeightMultiplier = 1.2) => { if (!text) return { height: 0, lines: [] }; ctx.fillStyle = color; ctx.font = `${isBold ? 'bold ' : ''}${fontSize}px Arial, sans-serif`; const words = text.split(' '); let line = '', lines = [], currentY = y; for (const word of words) { const testLine = line + word + ' '; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && line) { lines.push(line.trim()); line = word + ' '; } else { line = testLine; } } lines.push(line.trim()); lines.forEach((line, i) => { if (line) ctx.fillText(line, x, currentY + i * fontSize * lineHeightMultiplier); }); return { height: lines.length * fontSize * lineHeightMultiplier, lines }; }; const drawImageAndFilter = (image, offsetX = 0, availableWidth = canvas.width, availableHeight = canvas.height) => { if (!image || !image.width || !image.height) { console.error('Imagen inválida para dibujar'); return; } const imgRatio = image.width / image.height; const canvasRatio = availableWidth / availableHeight; let w, h, x, y; if (imgRatio > canvasRatio) { h = availableHeight * config.zoom; w = h * imgRatio; } else { w = availableWidth * config.zoom; h = w / imgRatio; } x = offsetX + (availableWidth - w) / 2 + config.zoomX; y = (availableHeight - h) / 2 + config.zoomY; ctx.drawImage(image, x, y, w, h); console.log('Imagen dibujada en posición:', x, y, w, h); if (config.filter) { ctx.fillStyle = hexToRgba('#000000', config.filterOpacity); ctx.fillRect(offsetX, 0, availableWidth, availableHeight); console.log('Filtro negro aplicado'); } }; const drawLogo = () => { if (!config.showLogo || !config.logoUrl) return; const logo = new Image(); logo.crossOrigin = 'anonymous'; logo.onload = () => { ctx.drawImage(logo, config.logoLeft, config.logoTop, config.logoSize, config.logoSize); console.log('Logo dibujado exitosamente'); }; logo.onerror = () => console.warn('Error cargando logo:', config.logoUrl); logo.src = config.logoUrl + '?v=' + new Date().getTime(); // Evitar caché }; const drawCommonText = (customConfig = {}) => { const C = { ...config, ...customConfig }; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; let currentTextY = C.catTop || C.titleTop; if (C.showCat && C.category) { ctx.fillStyle = C.titleColor; ctx.font = `bold ${C.catSize}px Arial, sans-serif`; const catInfo = wrapText(C.category, C.leftMargin, currentTextY, canvas.width - C.leftMargin * 2, C.catSize, C.titleColor, true); currentTextY += catInfo.height + 10; console.log('Categoría dibujada:', C.category); } if (C.showWeb && C.websiteUrl) { ctx.fillStyle = C.titleColor; ctx.font = `${C.webSize}px Arial, sans-serif`; const webInfo = wrapText(C.websiteUrl, C.leftMargin, currentTextY, canvas.width - C.leftMargin * 2, C.webSize, C.titleColor); currentTextY += webInfo.height + 10; console.log('Sitio web dibujado:', C.websiteUrl); } if (C.showTitle && C.title) { wrapText(C.title, C.leftMargin, currentTextY, canvas.width - C.leftMargin * 2, C.titleSize, C.titleColor, true); console.log('Título dibujado:', C.title); } }; const drawFallback = () => { const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#667eea'); gradient.addColorStop(1, '#764ba2'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 24px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('⚠️ Imagen no disponible', canvas.width / 2, canvas.height / 2 - 40); ctx.font = '16px Arial, sans-serif'; ctx.fillText('El post no tiene imagen destacada', canvas.width / 2, canvas.height / 2 - 10); ctx.fillText('o la URL no es accesible', canvas.width / 2, canvas.height / 2 + 15); drawCommonText(); // Dibujar texto incluso en fallback drawLogo(); console.log('Fallback dibujado'); }; const mainImage = new Image(); mainImage.crossOrigin = 'anonymous'; mainImage.onload = () => { console.log('Imagen cargada exitosamente:', config.imageUrl); try { ctx.save(); switch (config.style) { case 'style1': drawImageAndFilter(mainImage); ctx.fillStyle = config.lineColor; ctx.fillRect(config.lineMarginLeft, config.lineMarginTop, config.lineWidth, canvas.height - config.lineMarginTop - config.lineMarginBottom); drawCommonText(); break; case 'style2': drawImageAndFilter(mainImage); const bannerWidth2 = canvas.width * 0.45; const bannerHeight2 = canvas.height * 0.08; ctx.fillStyle = config.bannerColor || '#DC2626'; ctx.fillRect(config.leftMargin, config.logoTop, bannerWidth2, bannerHeight2); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${config.bannerTextSize || 14}px Arial, sans-serif`; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(config.websiteUrl, config.leftMargin + 15, config.logoTop + bannerHeight2 / 2); ctx.fillStyle = config.titleColor; ctx.font = `bold ${config.titleSize}px Arial, sans-serif`; ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 4; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; const textInfo = wrapText(config.title, config.leftMargin, config.titleTop, canvas.width - config.leftMargin * 2, config.titleSize, config.titleColor, true, 1.3); ctx.shadowColor = 'transparent'; ctx.strokeStyle = config.bannerColor || '#DC2626'; ctx.lineWidth = config.underlineWidth || 4; ctx.beginPath(); ctx.moveTo(config.leftMargin, config.titleTop + textInfo.height + 15); ctx.lineTo(config.leftMargin + (canvas.width - config.leftMargin * 2) * 0.8, config.titleTop + textInfo.height + 15); ctx.stroke(); if (config.showLogo) { const logoSize = canvas.width * 0.08; ctx.fillStyle = 'rgba(255,255,255,0.9)'; ctx.fillRect(canvas.width - logoSize - config.leftMargin, config.logoTop, logoSize, logoSize); ctx.strokeStyle = '#CCCCCC'; ctx.lineWidth = 2; ctx.strokeRect(canvas.width - logoSize - config.leftMargin, config.logoTop, logoSize, logoSize); ctx.fillStyle = '#666666'; ctx.font = `${logoSize * 0.3}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('Logo', canvas.width - logoSize - config.leftMargin + logoSize / 2, config.logoTop + logoSize / 2); } break; case 'style3': drawImageAndFilter(mainImage); const grad = ctx.createLinearGradient(0, 0, 0, canvas.height); grad.addColorStop(0, hexToRgba(config.gradColor1, 0)); grad.addColorStop(1, hexToRgba(config.gradColor2, config.gradOpacity)); ctx.fillStyle = grad; ctx.fillRect(0, 0, canvas.width, canvas.height); drawCommonText(); break; case 'style4': drawImageAndFilter(mainImage); ctx.shadowColor = config.minShadowColor; ctx.shadowBlur = config.minShadowBlur; drawCommonText({ catTop: config.catTop, webTop: config.catTop + config.catSize + config.minSpacing, titleTop: config.catTop + config.catSize + config.minSpacing + config.webSize + config.minSpacing }); ctx.shadowColor = 'transparent'; break; case 'style5': const sidebarPx = Math.min(canvas.width * (config.sideWidth / 100), canvas.width * 0.5); drawImageAndFilter(mainImage, sidebarPx, canvas.width - sidebarPx, canvas.height); const sidebarGradient = ctx.createLinearGradient(0, 0, sidebarPx, 0); sidebarGradient.addColorStop(0, config.sideColor); sidebarGradient.addColorStop(1, hexToRgba(config.sideColor, 0.9)); ctx.fillStyle = sidebarGradient; ctx.fillRect(0, 0, sidebarPx, canvas.height); ctx.save(); ctx.translate(sidebarPx / 2, canvas.height / 2); ctx.rotate(-Math.PI / 2); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${config.sideFontSize}px ${config.sidebarFontFamily}, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowBlur = 6; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 3; ctx.fillText(config.sideText.toUpperCase(), 0, 0); ctx.restore(); let currentY = config.catTop + 20; const textLeft = sidebarPx + 40, maxTextWidth = canvas.width - textLeft - 40; if (config.showCat) { ctx.font = `bold ${config.catSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; ctx.shadowColor = 'rgba(0,0,0,0.4)'; ctx.shadowBlur = 4; const catInfo = wrapText(config.category, textLeft, currentY, maxTextWidth, config.catSize, config.titleColor, true); currentY += catInfo.height + 15; } if (config.showWeb) { ctx.font = `${config.webSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; const webInfo = wrapText(config.websiteUrl, textLeft, currentY, maxTextWidth, config.webSize, config.titleColor); currentY += webInfo.height + 20; } if (config.showTitle) { ctx.font = `bold ${config.titleSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; wrapText(config.title, textLeft, currentY, maxTextWidth, config.titleSize, config.titleColor, true); } ctx.shadowColor = 'transparent'; break; case 'style6': ctx.fillStyle = config.backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); const imageHeight = canvas.height * 0.6; drawImageAndFilter(mainImage, 0, canvas.width, imageHeight); if (config.filter) { ctx.fillStyle = hexToRgba('#000000', config.filterOpacity); ctx.fillRect(0, 0, canvas.width, imageHeight); } const bannerY = imageHeight - (canvas.height * 0.06); const bannerWidth = canvas.width * 0.8, bannerHeight = canvas.height * 0.12; ctx.fillStyle = config.bannerColor; ctx.fillRect(config.leftMargin, bannerY, bannerWidth, bannerHeight); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${config.bannerTextSize}px Arial, sans-serif`; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(config.siteName, config.leftMargin + 20, bannerY + bannerHeight / 2); let textBlockY = bannerY + bannerHeight + 20; const textBlockX = config.leftMargin, textBlockWidth = canvas.width - config.leftMargin * 2; ctx.fillStyle = config.backgroundColor; ctx.fillRect(textBlockX - 10, textBlockY - 10, textBlockWidth + 20, canvas.height - textBlockY - 20); let currentTextY = textBlockY + config.catTop - 60; // Ajustar para usar config.catTop relativo if (config.showCat) { ctx.font = `bold ${config.catSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; const catInfo = wrapText(config.category, textBlockX, currentTextY, textBlockWidth, config.catSize, config.titleColor, true); currentTextY += catInfo.height + 10; } if (config.showWeb) { ctx.font = `${config.webSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; const webInfo = wrapText(config.websiteUrl, textBlockX, currentTextY, textBlockWidth, config.webSize, config.titleColor); currentTextY += webInfo.height + 10; } if (config.showTitle) { ctx.font = `bold ${config.titleSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; const titleInfo = wrapText(config.title, textBlockX, currentTextY, textBlockWidth, config.titleSize, config.titleColor, true, 1.4); currentTextY += titleInfo.height + 20; } const dmarbellaBarWidth = canvas.width * 0.4, dmarbellaBarHeight = canvas.height * 0.08; const dmarbellaBarX = config.leftMargin, dmarbellaBarY = textBlockY - dmarbellaBarHeight + 20; ctx.fillStyle = '#1E3A8A'; ctx.fillRect(dmarbellaBarX, dmarbellaBarY, dmarbellaBarWidth, dmarbellaBarHeight); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${config.dmarbellaTextSize}px Arial, sans-serif`; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('DMarbella', dmarbellaBarX + 15, dmarbellaBarY + dmarbellaBarHeight / 2); const buttonWidth = canvas.width * 0.35, buttonHeight = 40; const buttonX = canvas.width - buttonWidth - config.leftMargin, buttonY = currentTextY + 20; ctx.fillStyle = config.buttonColor; ctx.fillRect(buttonX, buttonY, buttonWidth, buttonHeight); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${config.webSize}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const buttonText = `Leer más en ${config.websiteUrl}`; ctx.fillText(buttonText, buttonX + buttonWidth / 2, buttonY + buttonHeight / 2); if (config.showLogo) { const logoSize = canvas.height * 0.08; // Ajuste para feed/story ctx.fillStyle = '#1E3A8A'; ctx.fillRect(canvas.width - logoSize - config.leftMargin, config.logoTop, logoSize, logoSize); ctx.fillStyle = '#FFFFFF'; ctx.font = `bold ${logoSize * 0.25}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('NEWS', canvas.width - logoSize - config.leftMargin + logoSize / 2, config.logoTop + logoSize / 2); } break; case 'style7': drawImageAndFilter(mainImage); const drawDIMOText = (text, x, y, maxWidth, fontSize, color, bgColor, isBold = false) => { if (!text) return { height: 0 }; ctx.font = `${isBold ? 'bold ' : ''}${fontSize}px Arial, sans-serif`; const words = text.split(' '); let line = '', lines = [], currentY = y; for (const word of words) { const testLine = line + word + ' '; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && line) { lines.push(line.trim()); line = word + ' '; } else { line = testLine; } } lines.push(line.trim()); lines.forEach((lineText, i) => { if (lineText) { const metrics = ctx.measureText(lineText); ctx.fillStyle = hexToRgba(bgColor, 0.7); ctx.fillRect(x - 5, currentY + i * fontSize * 1.2 - fontSize + 5, metrics.width + 10, fontSize * 1.2); ctx.fillStyle = color; ctx.fillText(lineText, x, currentY + i * fontSize * 1.2); } }); return { height: lines.length * fontSize * 1.2 }; }; let currentY7 = config.catTop; if (config.showCat) { const catHeight = drawDIMOText(config.category, config.leftMargin + 20, currentY7, canvas.width - config.leftMargin * 2 - 40, config.catSize, config.titleColor, config.blockSubColor, true).height; currentY7 += catHeight + 10; } if (config.showWeb) { const webHeight = drawDIMOText(config.websiteUrl, config.leftMargin + 20, currentY7, canvas.width - config.leftMargin * 2 - 40, config.webSize, config.titleColor, config.blockSubColor).height; currentY7 += webHeight + 10; } if (config.showTitle) { drawDIMOText(config.title, config.leftMargin + 20, currentY7, canvas.width - config.leftMargin * 2 - 40, config.titleSize, config.titleColor, config.blockMainColor, true); } break; case 'style8': drawImageAndFilter(mainImage); const centralGrad = ctx.createLinearGradient(0, canvas.height / 2, 0, canvas.height); centralGrad.addColorStop(0, hexToRgba(config.centralGradColor, 0)); centralGrad.addColorStop(1, hexToRgba(config.centralGradColor, 0.8)); ctx.fillStyle = centralGrad; ctx.fillRect(0, canvas.height / 2, canvas.width, canvas.height / 2); ctx.textAlign = 'center'; if (config.showCat) { ctx.font = `bold ${config.catSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; ctx.fillText(config.category, canvas.width / 2, canvas.height * 0.55); } if (config.showTitle) { wrapText(config.title, canvas.width / 2, canvas.height * 0.6, canvas.width * 0.8, config.titleSize, config.titleColor, true); } if (config.showWeb) { ctx.font = `${config.webSize}px Arial, sans-serif`; ctx.fillStyle = config.titleColor; ctx.fillText(config.websiteUrl, canvas.width / 2, canvas.height * 0.7); } break; default: console.warn('Estilo no reconocido:', config.style); drawCommonText(); break; } ctx.restore(); drawLogo(); } catch (error) { console.error('Error generando imagen:', error); drawFallback(); } }; mainImage.onerror = (error) => { console.error('Error cargando imagen:', config.imageUrl, error); if (mainImage.crossOrigin === 'anonymous') { console.log('Intentando sin CORS...'); mainImage.crossOrigin = null; mainImage.src = config.imageUrl + '?v=' + new Date().getTime(); return; } drawFallback(); }; if (config.imageUrl && config.imageUrl.trim()) { console.log('Cargando imagen desde:', config.imageUrl); mainImage.src = config.imageUrl + '?v=' + new Date().getTime(); // Evitar caché } else { console.error('URL de imagen inválida'); drawFallback(); } // Dibujar texto común incluso si la imagen falla drawCommonText(); }; // Funciones de Zoom window.updateDisplayZoom = (uniqueId, zoomValue) => { const canvas = document.getElementById(`canvas_${uniqueId}`); const zoomSpan = document.getElementById(`display_zoom_value_${uniqueId}`); if (canvas && zoomSpan) { const zoom = parseFloat(zoomValue); if (!isNaN(zoom) && zoom >= 0.1 && zoom <= 2.0) { canvas.style.width = `${canvas.width * zoom}px`; canvas.style.height = `${canvas.height * zoom}px`; zoomSpan.textContent = `${Math.round(zoom * 100)}%`; } } }; window.resetDisplayZoom = (uniqueId) => { const canvas = document.getElementById(`canvas_${uniqueId}`); const zoomSlider = document.getElementById(`display_zoom_${uniqueId}`); const zoomSpan = document.getElementById(`display_zoom_value_${uniqueId}`); if (canvas && zoomSlider && zoomSpan) { canvas.style.width = `${canvas.width}px`; canvas.style.height = `${canvas.height}px`; zoomSlider.value = '1.0'; zoomSpan.textContent = '100%'; } }; window.updateZoom = (canvasId, zoomValue) => { const canvas = document.getElementById(canvasId); const zoomSpan = document.getElementById(`zoom_value_${canvasId.replace('canvas_', '')}`); if (canvas && zoomSpan) { const zoom = parseFloat(zoomValue); if (!isNaN(zoom) && zoom >= 0.5 && zoom <= 3.0) { canvas.dataset.zoomLevel = zoom; zoomSpan.textContent = `${zoom.toFixed(1)}x`; generateSocialImage(canvas); } } }; window.resetZoom = (canvasId) => { const canvas = document.getElementById(canvasId); const zoomSlider = document.getElementById(`zoom_${canvasId.replace('canvas_', '')}`); const zoomSpan = document.getElementById(`zoom_value_${canvasId.replace('canvas_', '')}`); if (canvas && zoomSlider && zoomSpan) { canvas.dataset.zoomLevel = '1.0'; canvas.dataset.zoomX = '0'; canvas.dataset.zoomY = '0'; zoomSlider.value = '1.0'; zoomSpan.textContent = '1.0x'; generateSocialImage(canvas); } }; window.enableImageDragging = (canvas) => { let isDragging = false, lastX, lastY; const moveHandler = (e) => { if (!isDragging) return; const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width, scaleY = canvas.height / rect.height; const currentX = (e.clientX || e.touches?.[0]?.clientX) - rect.left; const currentY = (e.clientY || e.touches?.[0]?.clientY) - rect.top; canvas.dataset.zoomX = parseFloat(canvas.dataset.zoomX) + (currentX - lastX) * scaleX; canvas.dataset.zoomY = parseFloat(canvas.dataset.zoomY) + (currentY - lastY) * scaleY; generateSocialImage(canvas); lastX = currentX; lastY = currentY; }; const startHandler = (e) => { if (parseFloat(canvas.dataset.zoomLevel) <= 1.0) return; isDragging = true; const rect = canvas.getBoundingClientRect(); lastX = (e.clientX || e.touches?.[0]?.clientX) - rect.left; lastY = (e.clientY || e.touches?.[0]?.clientY) - rect.top; canvas.style.cursor = 'grabbing'; e.preventDefault(); }; const endHandler = () => { isDragging = false; canvas.style.cursor = parseFloat(canvas.dataset.zoomLevel) > 1.0 ? 'grab' : 'default'; }; canvas.addEventListener('mousedown', startHandler); canvas.addEventListener('mousemove', moveHandler); canvas.addEventListener('mouseup', endHandler); canvas.addEventListener('mouseleave', endHandler); canvas.addEventListener('touchstart', startHandler, { passive: false }); canvas.addEventListener('touchmove', moveHandler, { passive: false }); canvas.addEventListener('touchend', endHandler); };