UNPKG

@cognima/banners

Version:

Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações

1,215 lines (1,047 loc) 41.9 kB
"use strict"; /** * Módulo de banners para E-commerce * * Este módulo fornece funções para criar banners de e-commerce * com diferentes estilos e opções de personalização. * * @module e-commerce-banner * @author Cognima Team (melhorado) * @version 2.0.0 */ const PImage = require('pureimage'); const fs = require('fs'); const path = require('path'); const utils = require('../utils'); const constants = require('./constants'); const effects = require('./effects'); const imageProcessor = require('./image-processor'); const imageFilters = require('./image-filters'); const validator = require('./validator'); /** * Cria um banner de produto para e-commerce * * @async * @param {Object} options - Opções de configuração * @param {string} options.productName - Nome do produto * @param {string} options.productImageURL - URL da imagem do produto * @param {string} [options.price] - Preço do produto * @param {string} [options.currency="R$"] - Símbolo da moeda * @param {string} [options.discountBadge] - Texto do badge de desconto (ex: "20% OFF") * @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action * @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo * @param {string} [options.accentColor="#FF6B6B"] - Cor de destaque * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "gradient") * @param {number} [options.width=1200] - Largura do banner em pixels * @param {number} [options.height=628] - Altura do banner em pixels * @param {string} [options.font="Poppins"] - Nome da fonte * @param {boolean} [options.showRating=false] - Se deve mostrar classificação por estrelas * @param {number} [options.rating=5] - Classificação do produto (1-5) * @param {boolean} [options.showBorder=true] - Se deve mostrar borda * @param {string} [options.borderColor="#EEEEEE"] - Cor da borda * @param {number} [options.borderWidth=2] - Largura da borda em pixels * @param {number} [options.borderRadius=10] - Raio da borda em pixels * @param {boolean} [options.showShadow=true] - Se deve mostrar sombra * @param {string} [options.shadowColor="rgba(0,0,0,0.1)"] - Cor da sombra * @param {Array<string>} [options.tags=[]] - Tags do produto * @param {string} [options.backgroundImageURL] - URL da imagem de fundo * @param {number} [options.backgroundOpacity=0.1] - Opacidade da imagem de fundo * @param {boolean} [options.applyFilter=false] - Se deve aplicar filtro à imagem do produto * @param {string} [options.filterType="none"] - Tipo de filtro ("none", "grayscale", "sepia", "blur", etc.) * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createProductBanner(options) { // Validação de parâmetros validator.validateRequiredParams(options, ['productName', 'productImageURL']); // Valores padrão const defaults = { price: '', currency: 'R$', discountBadge: '', ctaText: 'COMPRAR AGORA', backgroundColor: '#FFFFFF', accentColor: '#FF6B6B', textColor: '#333333', style: 'standard', width: 1200, height: 628, font: 'Poppins', showRating: false, rating: 5, showBorder: true, borderColor: '#EEEEEE', borderWidth: 2, borderRadius: 10, showShadow: true, shadowColor: 'rgba(0,0,0,0.1)', tags: [], backgroundImageURL: '', backgroundOpacity: 0.1, applyFilter: false, filterType: 'none' }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Cria a imagem const img = PImage.make(settings.width, settings.height); const ctx = img.getContext('2d'); try { // Aplica o estilo de acordo com a opção selecionada switch (settings.style) { case 'minimal': await drawMinimalStyle(ctx, settings); break; case 'bold': await drawBoldStyle(ctx, settings); break; case 'gradient': await drawGradientStyle(ctx, settings); break; case 'standard': default: await drawStandardStyle(ctx, settings); break; } // Retorna o buffer da imagem return await utils.getBufferFromImage(img); } catch (error) { console.error('Erro ao criar banner de produto:', error); throw error; } } /** * Desenha o estilo padrão do banner de produto * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawStandardStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Se tiver imagem de fundo, desenha com opacidade if (settings.backgroundImageURL) { const backgroundImage = await utils.loadImage(settings.backgroundImageURL); ctx.globalAlpha = settings.backgroundOpacity; ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height); ctx.globalAlpha = 1.0; } // Carrega a imagem do produto let productImage = await utils.loadImage(settings.productImageURL); // Aplica filtro se necessário if (settings.applyFilter && settings.filterType !== 'none') { productImage = await imageFilters.applyFilter( productImage, settings.filterType ); } // Calcula as dimensões e posições const productWidth = settings.width * 0.4; const productHeight = settings.height * 0.7; const productX = settings.width * 0.1; const productY = (settings.height - productHeight) / 2; // Desenha a imagem do produto utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight); // Desenha o conteúdo de texto const textX = productX + productWidth + 50; const textWidth = settings.width - textX - 50; // Nome do produto ctx.font = `bold 48px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60); // Preço if (settings.price) { ctx.font = `bold 64px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5); } // Badge de desconto if (settings.discountBadge) { const badgeWidth = 120; const badgeHeight = 40; const badgeX = textX; const badgeY = settings.height * 0.55; // Desenha o fundo do badge ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 5, true); // Desenha o texto do badge ctx.font = `bold 20px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; ctx.textAlign = 'center'; ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 28); ctx.textAlign = 'left'; } // Classificação por estrelas if (settings.showRating) { const starSize = 25; const starSpacing = 5; const startX = textX; const startY = settings.height * 0.6; for (let i = 0; i < 5; i++) { const starX = startX + i * (starSize + starSpacing); const filled = i < settings.rating; // Desenha estrela ctx.fillStyle = filled ? '#FFD700' : '#DDDDDD'; utils.drawStar(ctx, starX, startY, starSize); } } // Tags if (settings.tags.length > 0) { const tagHeight = 30; const tagSpacing = 10; const tagY = settings.height * 0.65; let currentX = textX; ctx.font = `16px ${settings.font}`; for (const tag of settings.tags) { const tagWidth = ctx.measureText(tag).width + 20; // Desenha o fundo da tag ctx.fillStyle = utils.adjustColor(settings.accentColor, 0.2); utils.roundRect(ctx, currentX, tagY, tagWidth, tagHeight, 15, true); // Desenha o texto da tag ctx.fillStyle = settings.textColor; ctx.textAlign = 'center'; ctx.fillText(tag, currentX + tagWidth / 2, tagY + 20); ctx.textAlign = 'left'; currentX += tagWidth + tagSpacing; // Quebra para a próxima linha se necessário if (currentX + tagWidth > textX + textWidth) { break; } } } // Botão CTA const ctaWidth = 250; const ctaHeight = 60; const ctaX = textX; const ctaY = settings.height * 0.75; // Desenha o fundo do botão ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true); // Desenha o texto do botão ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; ctx.textAlign = 'center'; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38); ctx.textAlign = 'left'; // Desenha borda se necessário if (settings.showBorder) { ctx.strokeStyle = settings.borderColor; ctx.lineWidth = settings.borderWidth; utils.roundRect(ctx, settings.borderWidth / 2, settings.borderWidth / 2, settings.width - settings.borderWidth, settings.height - settings.borderWidth, settings.borderRadius, false, true); } } /** * Desenha o estilo minimalista do banner de produto * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawMinimalStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Carrega a imagem do produto let productImage = await utils.loadImage(settings.productImageURL); // Aplica filtro se necessário if (settings.applyFilter && settings.filterType !== 'none') { productImage = await imageFilters.applyFilter( productImage, settings.filterType ); } // Calcula as dimensões e posições const productWidth = settings.width * 0.35; const productHeight = settings.height * 0.65; const productX = settings.width * 0.55; const productY = (settings.height - productHeight) / 2; // Desenha a imagem do produto utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight); // Desenha o conteúdo de texto const textX = settings.width * 0.1; const textWidth = productX - textX - 50; // Nome do produto ctx.font = `300 42px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText(ctx, settings.productName, textX, settings.height * 0.35, textWidth, 50); // Preço if (settings.price) { ctx.font = `bold 54px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.55); } // Badge de desconto if (settings.discountBadge) { ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.fillText(settings.discountBadge, textX, settings.height * 0.65); } // Botão CTA const ctaWidth = 200; const ctaHeight = 50; const ctaX = textX; const ctaY = settings.height * 0.75; // Desenha a borda do botão ctx.strokeStyle = settings.accentColor; ctx.lineWidth = 2; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true); // Desenha o texto do botão ctx.font = `bold 20px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = 'center'; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33); ctx.textAlign = 'left'; } /** * Desenha o estilo bold do banner de produto * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawBoldStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.accentColor; ctx.fillRect(0, 0, settings.width, settings.height); // Carrega a imagem do produto let productImage = await utils.loadImage(settings.productImageURL); // Aplica filtro se necessário if (settings.applyFilter && settings.filterType !== 'none') { productImage = await imageFilters.applyFilter( productImage, settings.filterType ); } // Calcula as dimensões e posições const productWidth = settings.width * 0.45; const productHeight = settings.height * 0.8; const productX = settings.width * 0.5; const productY = (settings.height - productHeight) / 2; // Desenha a imagem do produto utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight); // Desenha o conteúdo de texto const textX = settings.width * 0.08; const textWidth = productX - textX - 30; // Nome do produto ctx.font = `bold 52px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; utils.wrapText(ctx, settings.productName.toUpperCase(), textX, settings.height * 0.3, textWidth, 60); // Preço if (settings.price) { ctx.font = `bold 72px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5); } // Badge de desconto if (settings.discountBadge) { const badgeWidth = 140; const badgeHeight = 50; const badgeX = textX; const badgeY = settings.height * 0.55; // Desenha o fundo do badge ctx.fillStyle = '#FFFFFF'; utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true); // Desenha o texto do badge ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = 'center'; ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35); ctx.textAlign = 'left'; } // Botão CTA const ctaWidth = 250; const ctaHeight = 70; const ctaX = textX; const ctaY = settings.height * 0.7; // Desenha o fundo do botão ctx.fillStyle = '#FFFFFF'; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true); // Desenha o texto do botão ctx.font = `bold 26px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = 'center'; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43); ctx.textAlign = 'left'; } /** * Desenha o estilo gradient do banner de produto * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawGradientStyle(ctx, settings) { // Cria um gradiente de fundo const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, settings.accentColor); gradient.addColorStop(1, utils.adjustColor(settings.accentColor, -0.3)); // Desenha o fundo ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona um padrão de pontos ao fundo effects.addDotPattern(ctx, settings.width, settings.height, 20, 'rgba(255,255,255,0.1)'); // Carrega a imagem do produto let productImage = await utils.loadImage(settings.productImageURL); // Aplica filtro se necessário if (settings.applyFilter && settings.filterType !== 'none') { productImage = await imageFilters.applyFilter( productImage, settings.filterType ); } // Calcula as dimensões e posições const productWidth = settings.width * 0.4; const productHeight = settings.height * 0.75; const productX = settings.width * 0.05; const productY = (settings.height - productHeight) / 2; // Adiciona sombra à imagem do produto if (settings.showShadow) { ctx.shadowColor = settings.shadowColor; ctx.shadowBlur = 30; ctx.shadowOffsetX = 10; ctx.shadowOffsetY = 10; } // Desenha a imagem do produto utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight); // Remove a sombra ctx.shadowColor = 'transparent'; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; // Desenha o conteúdo de texto const textX = productX + productWidth + 60; const textWidth = settings.width - textX - 60; // Adiciona um retângulo semi-transparente para o texto ctx.fillStyle = 'rgba(255,255,255,0.15)'; utils.roundRect(ctx, textX - 20, settings.height * 0.2, textWidth + 40, settings.height * 0.6, 20, true); // Nome do produto ctx.font = `bold 48px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60); // Preço if (settings.price) { ctx.font = `bold 64px ${settings.font}`; ctx.fillStyle = '#FFFFFF'; // Adiciona sombra ao texto do preço ctx.shadowColor = 'rgba(0,0,0,0.3)'; ctx.shadowBlur = 5; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5); // Remove a sombra ctx.shadowColor = 'transparent'; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; } // Badge de desconto if (settings.discountBadge) { const badgeWidth = 140; const badgeHeight = 50; const badgeX = textX; const badgeY = settings.height * 0.55; // Desenha o fundo do badge ctx.fillStyle = '#FFFFFF'; utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true); // Desenha o texto do badge ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = 'center'; ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35); ctx.textAlign = 'left'; } // Botão CTA const ctaWidth = 250; const ctaHeight = 70; const ctaX = textX; const ctaY = settings.height * 0.7; // Desenha o fundo do botão com gradiente const ctaGradient = ctx.createLinearGradient(ctaX, ctaY, ctaX + ctaWidth, ctaY + ctaHeight); ctaGradient.addColorStop(0, '#FFFFFF'); ctaGradient.addColorStop(1, '#F0F0F0'); ctx.fillStyle = ctaGradient; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true); // Desenha o texto do botão ctx.font = `bold 26px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = 'center'; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43); ctx.textAlign = 'left'; } /** * Cria um banner de promoção para e-commerce * * @async * @param {Object} options - Opções de configuração * @param {string} options.title - Título da promoção * @param {string} [options.subtitle] - Subtítulo da promoção * @param {string} [options.backgroundImageURL] - URL da imagem de fundo * @param {string} [options.discountText] - Texto de desconto (ex: "50% OFF") * @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action * @param {string} [options.validUntil] - Data de validade da promoção * @param {string} [options.backgroundColor="#FF6B6B"] - Cor de fundo * @param {string} [options.textColor="#FFFFFF"] - Cor do texto * @param {string} [options.accentColor="#FFFFFF"] - Cor de destaque * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "seasonal") * @param {number} [options.width=1200] - Largura do banner em pixels * @param {number} [options.height=628] - Altura do banner em pixels * @param {string} [options.font="Poppins"] - Nome da fonte * @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem de fundo * @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição * @param {string} [options.overlayColor="#000000"] - Cor da sobreposição * @param {string} [options.season] - Temporada para o estilo seasonal ("christmas", "halloween", "summer", "blackfriday") * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createPromotionBanner(options) { // Validação de parâmetros validator.validateRequiredParams(options, ['title']); // Valores padrão const defaults = { subtitle: '', backgroundImageURL: '', discountText: '', ctaText: 'COMPRAR AGORA', validUntil: '', backgroundColor: '#FF6B6B', textColor: '#FFFFFF', accentColor: '#FFFFFF', style: 'standard', width: 1200, height: 628, font: 'Poppins', showOverlay: true, overlayOpacity: 0.5, overlayColor: '#000000', season: '' }; // Mescla as opções com os valores padrão const settings = { ...defaults, ...options }; // Cria a imagem const img = PImage.make(settings.width, settings.height); const ctx = img.getContext('2d'); try { // Aplica o estilo de acordo com a opção selecionada switch (settings.style) { case 'minimal': await drawMinimalPromotionStyle(ctx, settings); break; case 'bold': await drawBoldPromotionStyle(ctx, settings); break; case 'seasonal': await drawSeasonalPromotionStyle(ctx, settings); break; case 'standard': default: await drawStandardPromotionStyle(ctx, settings); break; } // Retorna o buffer da imagem return await utils.getBufferFromImage(img); } catch (error) { console.error('Erro ao criar banner de promoção:', error); throw error; } } /** * Desenha o estilo padrão do banner de promoção * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawStandardPromotionStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Se tiver imagem de fundo, desenha com sobreposição if (settings.backgroundImageURL) { const backgroundImage = await utils.loadImage(settings.backgroundImageURL); ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height); // Adiciona sobreposição se necessário if (settings.showOverlay) { ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity); ctx.fillRect(0, 0, settings.width, settings.height); } } // Desenha o conteúdo de texto centralizado ctx.textAlign = 'center'; // Título ctx.font = `bold 72px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80); // Subtítulo if (settings.subtitle) { ctx.font = `300 36px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50); } // Texto de desconto if (settings.discountText) { const discountSize = 120; const discountX = settings.width / 2; const discountY = settings.height * 0.65; // Desenha um círculo para o desconto ctx.fillStyle = settings.accentColor; ctx.beginPath(); ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2); ctx.fill(); // Desenha o texto de desconto ctx.font = `bold 48px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.fillText(settings.discountText, discountX, discountY + 15); } // Botão CTA const ctaWidth = 300; const ctaHeight = 70; const ctaX = (settings.width - ctaWidth) / 2; const ctaY = settings.height * 0.8; // Desenha o fundo do botão ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true); // Desenha o texto do botão ctx.font = `bold 28px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45); // Data de validade if (settings.validUntil) { ctx.font = `16px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95); } // Restaura o alinhamento de texto ctx.textAlign = 'left'; } /** * Desenha o estilo minimalista do banner de promoção * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawMinimalPromotionStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona uma borda fina ctx.strokeStyle = settings.backgroundColor; ctx.lineWidth = 3; ctx.strokeRect(20, 20, settings.width - 40, settings.height - 40); // Desenha o conteúdo de texto centralizado ctx.textAlign = 'center'; // Título ctx.font = `bold 64px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; utils.wrapTextCentered(ctx, settings.title, settings.width / 2, settings.height * 0.35, settings.width * 0.7, 70); // Subtítulo if (settings.subtitle) { ctx.font = `300 32px ${settings.font}`; ctx.fillStyle = '#333333'; utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.6, 40); } // Texto de desconto if (settings.discountText) { ctx.font = `bold 96px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.7); } // Botão CTA const ctaWidth = 250; const ctaHeight = 60; const ctaX = (settings.width - ctaWidth) / 2; const ctaY = settings.height * 0.8; // Desenha a borda do botão ctx.strokeStyle = settings.backgroundColor; ctx.lineWidth = 2; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true); // Desenha o texto do botão ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 38); // Data de validade if (settings.validUntil) { ctx.font = `16px ${settings.font}`; ctx.fillStyle = '#333333'; ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95); } // Restaura o alinhamento de texto ctx.textAlign = 'left'; } /** * Desenha o estilo bold do banner de promoção * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawBoldPromotionStyle(ctx, settings) { // Cria um gradiente de fundo const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, settings.backgroundColor); gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.3)); // Desenha o fundo ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona elementos decorativos for (let i = 0; i < 5; i++) { const size = Math.random() * 200 + 100; const x = Math.random() * settings.width; const y = Math.random() * settings.height; ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.1); ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } // Desenha o conteúdo de texto centralizado ctx.textAlign = 'center'; // Texto de desconto if (settings.discountText) { ctx.font = `bold 120px ${settings.font}`; ctx.fillStyle = settings.accentColor; // Adiciona sombra ao texto ctx.shadowColor = 'rgba(0,0,0,0.3)'; ctx.shadowBlur = 10; ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.4); // Remove a sombra ctx.shadowColor = 'transparent'; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; } // Título ctx.font = `bold 72px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.55, settings.width * 0.8, 80); // Subtítulo if (settings.subtitle) { ctx.font = `300 36px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.7, settings.width * 0.7, 50); } // Botão CTA const ctaWidth = 350; const ctaHeight = 80; const ctaX = (settings.width - ctaWidth) / 2; const ctaY = settings.height * 0.8; // Desenha o fundo do botão ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 40, true); // Desenha o texto do botão ctx.font = `bold 32px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 50); // Data de validade if (settings.validUntil) { ctx.font = `18px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95); } // Restaura o alinhamento de texto ctx.textAlign = 'left'; } /** * Desenha o estilo seasonal do banner de promoção * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawSeasonalPromotionStyle(ctx, settings) { // Configurações específicas para cada temporada const seasonalSettings = { christmas: { backgroundColor: '#D42426', accentColor: '#0F8A5F', textColor: '#FFFFFF', decorations: ['snowflake', 'gift', 'tree'] }, halloween: { backgroundColor: '#FF6600', accentColor: '#000000', textColor: '#FFFFFF', decorations: ['pumpkin', 'ghost', 'bat'] }, summer: { backgroundColor: '#00BFFF', accentColor: '#FFD700', textColor: '#FFFFFF', decorations: ['sun', 'palm', 'wave'] }, blackfriday: { backgroundColor: '#000000', accentColor: '#FF0000', textColor: '#FFFFFF', decorations: ['tag', 'cart', 'discount'] } }; // Usa as configurações da temporada selecionada ou as configurações padrão const season = settings.season.toLowerCase(); const currentSeason = seasonalSettings[season] || { backgroundColor: settings.backgroundColor, accentColor: settings.accentColor, textColor: settings.textColor, decorations: [] }; // Desenha o fundo ctx.fillStyle = currentSeason.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Se tiver imagem de fundo, desenha com sobreposição if (settings.backgroundImageURL) { const backgroundImage = await utils.loadImage(settings.backgroundImageURL); ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height); // Adiciona sobreposição se necessário if (settings.showOverlay) { ctx.fillStyle = utils.hexToRgba(currentSeason.backgroundColor, settings.overlayOpacity); ctx.fillRect(0, 0, settings.width, settings.height); } } // Adiciona decorações sazonais if (currentSeason.decorations.length > 0) { await addSeasonalDecorations(ctx, settings, currentSeason.decorations); } // Desenha o conteúdo de texto centralizado ctx.textAlign = 'center'; // Título ctx.font = `bold 72px ${settings.font}`; ctx.fillStyle = currentSeason.textColor; utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80); // Subtítulo if (settings.subtitle) { ctx.font = `300 36px ${settings.font}`; ctx.fillStyle = currentSeason.textColor; utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50); } // Texto de desconto if (settings.discountText) { const discountSize = 130; const discountX = settings.width / 2; const discountY = settings.height * 0.65; // Desenha um círculo para o desconto ctx.fillStyle = currentSeason.accentColor; ctx.beginPath(); ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2); ctx.fill(); // Desenha o texto de desconto ctx.font = `bold 54px ${settings.font}`; ctx.fillStyle = currentSeason.textColor; ctx.fillText(settings.discountText, discountX, discountY + 15); } // Botão CTA const ctaWidth = 300; const ctaHeight = 70; const ctaX = (settings.width - ctaWidth) / 2; const ctaY = settings.height * 0.8; // Desenha o fundo do botão ctx.fillStyle = currentSeason.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true); // Desenha o texto do botão ctx.font = `bold 28px ${settings.font}`; ctx.fillStyle = currentSeason.textColor; ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45); // Data de validade if (settings.validUntil) { ctx.font = `16px ${settings.font}`; ctx.fillStyle = currentSeason.textColor; ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95); } // Restaura o alinhamento de texto ctx.textAlign = 'left'; } /** * Adiciona decorações sazonais ao banner * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner * @param {Array<string>} decorations - Lista de decorações a serem adicionadas */ async function addSeasonalDecorations(ctx, settings, decorations) { // Implementação simplificada - em uma versão real, carregaria imagens de decorações // ou desenharia formas específicas para cada tipo de decoração for (let i = 0; i < 10; i++) { const size = Math.random() * 50 + 20; const x = Math.random() * settings.width; const y = Math.random() * settings.height; ctx.fillStyle = utils.hexToRgba('#FFFFFF', 0.2); // Escolhe uma decoração aleatória da lista const decoration = decorations[Math.floor(Math.random() * decorations.length)]; switch (decoration) { case 'snowflake': drawSnowflake(ctx, x, y, size); break; case 'gift': drawGift(ctx, x, y, size); break; case 'tree': drawTree(ctx, x, y, size); break; case 'pumpkin': drawPumpkin(ctx, x, y, size); break; case 'ghost': drawGhost(ctx, x, y, size); break; case 'bat': drawBat(ctx, x, y, size); break; case 'sun': drawSun(ctx, x, y, size); break; case 'palm': drawPalm(ctx, x, y, size); break; case 'wave': drawWave(ctx, x, y, size); break; case 'tag': drawTag(ctx, x, y, size); break; case 'cart': drawCart(ctx, x, y, size); break; case 'discount': drawDiscount(ctx, x, y, size); break; default: // Desenha um círculo como fallback ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.fill(); break; } } } // Funções para desenhar decorações sazonais // Estas são implementações simplificadas - em uma versão real, seriam mais detalhadas function drawSnowflake(ctx, x, y, size) { ctx.beginPath(); for (let i = 0; i < 6; i++) { const angle = (Math.PI / 3) * i; ctx.moveTo(x, y); ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size); } ctx.stroke(); } function drawGift(ctx, x, y, size) { ctx.fillRect(x - size / 2, y - size / 2, size, size); ctx.fillRect(x - size / 4, y - size / 2 - size / 4, size / 2, size * 1.5); ctx.fillRect(x - size / 2 - size / 4, y - size / 4, size * 1.5, size / 2); } function drawTree(ctx, x, y, size) { ctx.beginPath(); ctx.moveTo(x, y - size); ctx.lineTo(x + size / 2, y); ctx.lineTo(x - size / 2, y); ctx.closePath(); ctx.fill(); ctx.fillRect(x - size / 6, y, size / 3, size / 2); } function drawPumpkin(ctx, x, y, size) { ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.fill(); ctx.fillRect(x - size / 10, y - size / 2 - size / 5, size / 5, size / 5); } function drawGhost(ctx, x, y, size) { ctx.beginPath(); ctx.arc(x, y - size / 3, size / 2, Math.PI, 0, true); ctx.lineTo(x + size / 2, y + size / 2); ctx.lineTo(x + size / 4, y + size / 4); ctx.lineTo(x, y + size / 2); ctx.lineTo(x - size / 4, y + size / 4); ctx.lineTo(x - size / 2, y + size / 2); ctx.closePath(); ctx.fill(); } function drawBat(ctx, x, y, size) { ctx.beginPath(); ctx.arc(x, y, size / 4, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(x - size / 2, y - size / 4, size / 3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(x + size / 2, y - size / 4, size / 3, 0, Math.PI * 2); ctx.fill(); } function drawSun(ctx, x, y, size) { ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.fill(); for (let i = 0; i < 8; i++) { const angle = (Math.PI / 4) * i; ctx.beginPath(); ctx.moveTo(x + Math.cos(angle) * size / 2, y + Math.sin(angle) * size / 2); ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size); ctx.stroke(); } } function drawPalm(ctx, x, y, size) { ctx.fillRect(x - size / 10, y, size / 5, size); for (let i = 0; i < 5; i++) { const angle = (Math.PI / 6) + (Math.PI / 12) * i; ctx.beginPath(); ctx.ellipse(x, y, size / 3, size, angle, 0, Math.PI); ctx.fill(); } } function drawWave(ctx, x, y, size) { ctx.beginPath(); ctx.moveTo(x - size, y); for (let i = 0; i < 4; i++) { const cp1x = x - size + (i * size / 2); const cp1y = y + ((i % 2 === 0) ? -size / 3 : size / 3); const cp2x = x - size + (i * size / 2) + size / 4; const cp2y = y + ((i % 2 === 0) ? size / 3 : -size / 3); const endX = x - size + ((i + 1) * size / 2); const endY = y; ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY); } ctx.stroke(); } function drawTag(ctx, x, y, size) { ctx.beginPath(); ctx.moveTo(x, y - size / 2); ctx.lineTo(x + size / 2, y); ctx.lineTo(x, y + size / 2); ctx.lineTo(x - size / 2, y); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.arc(x, y, size / 6, 0, Math.PI * 2); ctx.stroke(); } function drawCart(ctx, x, y, size) { ctx.beginPath(); ctx.moveTo(x - size / 2, y); ctx.lineTo(x + size / 2, y); ctx.lineTo(x + size / 3, y - size / 2); ctx.lineTo(x - size / 3, y - size / 2); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.arc(x - size / 4, y + size / 6, size / 6, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(x + size / 4, y + size / 6, size / 6, 0, Math.PI * 2); ctx.fill(); } function drawDiscount(ctx, x, y, size) { ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x - size / 3, y - size / 3); ctx.lineTo(x + size / 3, y + size / 3); ctx.stroke(); } module.exports = { createProductBanner, createPromotionBanner };