@cognima/banners 0.0.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
- package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
- package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
- package/assets/fonts/Others/ChirpRegular.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
- package/assets/placeholders/album_art.png +0 -0
- package/assets/placeholders/avatar.png +0 -0
- package/assets/placeholders/badge.jpg +0 -0
- package/assets/placeholders/badge.png +0 -0
- package/assets/placeholders/badge_2.jpg +0 -0
- package/assets/placeholders/badge_3.jpg +0 -0
- package/assets/placeholders/badge_4.jpg +0 -0
- package/assets/placeholders/badge_5.jpg +0 -0
- package/assets/placeholders/banner.jpeg +0 -0
- package/assets/placeholders/images.jpeg +0 -0
- package/index.js +153 -0
- package/package.json +34 -0
- package/src/animation-effects.js +631 -0
- package/src/cache-manager.js +258 -0
- package/src/community-banner.js +1536 -0
- package/src/constants.js +208 -0
- package/src/discord-profile.js +584 -0
- package/src/e-commerce-banner.js +1214 -0
- package/src/effects.js +355 -0
- package/src/error-handler.js +305 -0
- package/src/event-banner.js +1319 -0
- package/src/facebook-post.js +679 -0
- package/src/gradient-welcome.js +430 -0
- package/src/image-filters.js +1034 -0
- package/src/image-processor.js +1014 -0
- package/src/instagram-post.js +504 -0
- package/src/interactive-elements.js +1208 -0
- package/src/linkedin-post.js +658 -0
- package/src/marketing-banner.js +1089 -0
- package/src/minimalist-banner.js +892 -0
- package/src/modern-profile.js +755 -0
- package/src/performance-optimizer.js +216 -0
- package/src/telegram-header.js +544 -0
- package/src/test-runner.js +645 -0
- package/src/tiktok-post.js +713 -0
- package/src/twitter-header.js +604 -0
- package/src/validator.js +442 -0
- package/src/welcome-leave.js +445 -0
- package/src/whatsapp-status.js +386 -0
- package/src/youtube-thumbnail.js +681 -0
- package/utils.js +710 -0
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de banners para Marketing
|
|
5
|
+
*
|
|
6
|
+
* Este módulo fornece funções para criar banners de marketing
|
|
7
|
+
* com diferentes estilos e opções de personalização.
|
|
8
|
+
*
|
|
9
|
+
* @module marketing-banner
|
|
10
|
+
* @author Cognima Team (melhorado)
|
|
11
|
+
* @version 2.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const PImage = require('pureimage');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const utils = require('../utils');
|
|
18
|
+
const constants = require('./constants');
|
|
19
|
+
const effects = require('./effects');
|
|
20
|
+
const imageProcessor = require('./image-processor');
|
|
21
|
+
const imageFilters = require('./image-filters');
|
|
22
|
+
const validator = require('./validator');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cria um banner para anúncio de marketing
|
|
26
|
+
*
|
|
27
|
+
* @async
|
|
28
|
+
* @param {Object} options - Opções de configuração
|
|
29
|
+
* @param {string} options.headline - Título principal do anúncio
|
|
30
|
+
* @param {string} [options.subheadline] - Subtítulo do anúncio
|
|
31
|
+
* @param {string} [options.imageURL] - URL da imagem principal
|
|
32
|
+
* @param {string} [options.logoURL] - URL do logo da empresa
|
|
33
|
+
* @param {string} [options.ctaText="SAIBA MAIS"] - Texto do botão de call-to-action
|
|
34
|
+
* @param {string} [options.ctaURL] - URL para o botão de call-to-action
|
|
35
|
+
* @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
|
|
36
|
+
* @param {string} [options.textColor="#333333"] - Cor do texto
|
|
37
|
+
* @param {string} [options.accentColor="#FF6B6B"] - Cor de destaque
|
|
38
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "corporate")
|
|
39
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
40
|
+
* @param {number} [options.height=628] - Altura do banner em pixels
|
|
41
|
+
* @param {string} [options.font="Poppins"] - Nome da fonte
|
|
42
|
+
* @param {boolean} [options.showLogo=true] - Se deve mostrar o logo
|
|
43
|
+
* @param {number} [options.logoSize=80] - Tamanho do logo em pixels
|
|
44
|
+
* @param {string} [options.logoPosition="top-left"] - Posição do logo ("top-left", "top-right", "bottom-left", "bottom-right")
|
|
45
|
+
* @param {boolean} [options.showBorder=false] - Se deve mostrar borda
|
|
46
|
+
* @param {string} [options.borderColor="#EEEEEE"] - Cor da borda
|
|
47
|
+
* @param {number} [options.borderWidth=2] - Largura da borda em pixels
|
|
48
|
+
* @param {Array<string>} [options.tags=[]] - Tags do anúncio
|
|
49
|
+
* @param {boolean} [options.showOverlay=false] - Se deve mostrar sobreposição na imagem
|
|
50
|
+
* @param {number} [options.overlayOpacity=0.3] - Opacidade da sobreposição
|
|
51
|
+
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
|
|
52
|
+
* @param {boolean} [options.applyFilter=false] - Se deve aplicar filtro à imagem
|
|
53
|
+
* @param {string} [options.filterType="none"] - Tipo de filtro ("none", "grayscale", "sepia", "blur", etc.)
|
|
54
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
55
|
+
*/
|
|
56
|
+
async function createAdvertisementBanner(options) {
|
|
57
|
+
// Validação de parâmetros
|
|
58
|
+
validator.validateRequiredParams(options, ['headline']);
|
|
59
|
+
|
|
60
|
+
// Valores padrão
|
|
61
|
+
const defaults = {
|
|
62
|
+
subheadline: '',
|
|
63
|
+
imageURL: '',
|
|
64
|
+
logoURL: '',
|
|
65
|
+
ctaText: 'SAIBA MAIS',
|
|
66
|
+
ctaURL: '',
|
|
67
|
+
backgroundColor: '#FFFFFF',
|
|
68
|
+
textColor: '#333333',
|
|
69
|
+
accentColor: '#FF6B6B',
|
|
70
|
+
style: 'standard',
|
|
71
|
+
width: 1200,
|
|
72
|
+
height: 628,
|
|
73
|
+
font: 'Poppins',
|
|
74
|
+
showLogo: true,
|
|
75
|
+
logoSize: 80,
|
|
76
|
+
logoPosition: 'top-left',
|
|
77
|
+
showBorder: false,
|
|
78
|
+
borderColor: '#EEEEEE',
|
|
79
|
+
borderWidth: 2,
|
|
80
|
+
tags: [],
|
|
81
|
+
showOverlay: false,
|
|
82
|
+
overlayOpacity: 0.3,
|
|
83
|
+
overlayColor: '#000000',
|
|
84
|
+
applyFilter: false,
|
|
85
|
+
filterType: 'none'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Mescla as opções com os valores padrão
|
|
89
|
+
const settings = { ...defaults, ...options };
|
|
90
|
+
|
|
91
|
+
// Cria a imagem
|
|
92
|
+
const img = PImage.make(settings.width, settings.height);
|
|
93
|
+
const ctx = img.getContext('2d');
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
97
|
+
switch (settings.style) {
|
|
98
|
+
case 'minimal':
|
|
99
|
+
await drawMinimalAdStyle(ctx, settings);
|
|
100
|
+
break;
|
|
101
|
+
case 'bold':
|
|
102
|
+
await drawBoldAdStyle(ctx, settings);
|
|
103
|
+
break;
|
|
104
|
+
case 'corporate':
|
|
105
|
+
await drawCorporateAdStyle(ctx, settings);
|
|
106
|
+
break;
|
|
107
|
+
case 'standard':
|
|
108
|
+
default:
|
|
109
|
+
await drawStandardAdStyle(ctx, settings);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Retorna o buffer da imagem
|
|
114
|
+
return await utils.getBufferFromImage(img);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Erro ao criar banner de anúncio:', error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Desenha o estilo padrão do banner de anúncio
|
|
123
|
+
*
|
|
124
|
+
* @async
|
|
125
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
126
|
+
* @param {Object} settings - Configurações do banner
|
|
127
|
+
*/
|
|
128
|
+
async function drawStandardAdStyle(ctx, settings) {
|
|
129
|
+
// Desenha o fundo
|
|
130
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
131
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
132
|
+
|
|
133
|
+
// Se tiver imagem, desenha com sobreposição se necessário
|
|
134
|
+
if (settings.imageURL) {
|
|
135
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
136
|
+
|
|
137
|
+
// Aplica filtro se necessário
|
|
138
|
+
let processedImage = image;
|
|
139
|
+
if (settings.applyFilter && settings.filterType !== 'none') {
|
|
140
|
+
processedImage = await imageFilters.applyFilter(
|
|
141
|
+
image,
|
|
142
|
+
settings.filterType
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Desenha a imagem
|
|
147
|
+
utils.drawImageProp(ctx, processedImage, 0, 0, settings.width, settings.height);
|
|
148
|
+
|
|
149
|
+
// Adiciona sobreposição se necessário
|
|
150
|
+
if (settings.showOverlay) {
|
|
151
|
+
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity);
|
|
152
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Desenha o logo se necessário
|
|
157
|
+
if (settings.showLogo && settings.logoURL) {
|
|
158
|
+
await drawLogo(ctx, settings);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Calcula as posições para o texto
|
|
162
|
+
const textX = settings.width * 0.1;
|
|
163
|
+
const textWidth = settings.width * 0.8;
|
|
164
|
+
|
|
165
|
+
// Título principal
|
|
166
|
+
ctx.font = `bold 64px ${settings.font}`;
|
|
167
|
+
ctx.fillStyle = settings.textColor;
|
|
168
|
+
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 70);
|
|
169
|
+
|
|
170
|
+
// Subtítulo
|
|
171
|
+
if (settings.subheadline) {
|
|
172
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
173
|
+
ctx.fillStyle = settings.textColor;
|
|
174
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Botão CTA
|
|
178
|
+
const ctaWidth = 200;
|
|
179
|
+
const ctaHeight = 60;
|
|
180
|
+
const ctaX = textX;
|
|
181
|
+
const ctaY = settings.height * 0.75;
|
|
182
|
+
|
|
183
|
+
// Desenha o fundo do botão
|
|
184
|
+
ctx.fillStyle = settings.accentColor;
|
|
185
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true);
|
|
186
|
+
|
|
187
|
+
// Desenha o texto do botão
|
|
188
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
189
|
+
ctx.fillStyle = '#FFFFFF';
|
|
190
|
+
ctx.textAlign = 'center';
|
|
191
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
192
|
+
ctx.textAlign = 'left';
|
|
193
|
+
|
|
194
|
+
// Tags
|
|
195
|
+
if (settings.tags.length > 0) {
|
|
196
|
+
const tagHeight = 30;
|
|
197
|
+
const tagSpacing = 10;
|
|
198
|
+
const tagY = settings.height * 0.85;
|
|
199
|
+
let currentX = textX;
|
|
200
|
+
|
|
201
|
+
ctx.font = `16px ${settings.font}`;
|
|
202
|
+
|
|
203
|
+
for (const tag of settings.tags) {
|
|
204
|
+
const tagWidth = ctx.measureText(tag).width + 20;
|
|
205
|
+
|
|
206
|
+
// Desenha o fundo da tag
|
|
207
|
+
ctx.fillStyle = utils.adjustColor(settings.accentColor, 0.2);
|
|
208
|
+
utils.roundRect(ctx, currentX, tagY, tagWidth, tagHeight, 15, true);
|
|
209
|
+
|
|
210
|
+
// Desenha o texto da tag
|
|
211
|
+
ctx.fillStyle = settings.textColor;
|
|
212
|
+
ctx.textAlign = 'center';
|
|
213
|
+
ctx.fillText(tag, currentX + tagWidth / 2, tagY + 20);
|
|
214
|
+
ctx.textAlign = 'left';
|
|
215
|
+
|
|
216
|
+
currentX += tagWidth + tagSpacing;
|
|
217
|
+
|
|
218
|
+
// Quebra para a próxima linha se necessário
|
|
219
|
+
if (currentX + tagWidth > textX + textWidth) {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Desenha borda se necessário
|
|
226
|
+
if (settings.showBorder) {
|
|
227
|
+
ctx.strokeStyle = settings.borderColor;
|
|
228
|
+
ctx.lineWidth = settings.borderWidth;
|
|
229
|
+
utils.roundRect(ctx, settings.borderWidth / 2, settings.borderWidth / 2,
|
|
230
|
+
settings.width - settings.borderWidth, settings.height - settings.borderWidth,
|
|
231
|
+
0, false, true);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Desenha o estilo minimalista do banner de anúncio
|
|
237
|
+
*
|
|
238
|
+
* @async
|
|
239
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
240
|
+
* @param {Object} settings - Configurações do banner
|
|
241
|
+
*/
|
|
242
|
+
async function drawMinimalAdStyle(ctx, settings) {
|
|
243
|
+
// Desenha o fundo
|
|
244
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
245
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
246
|
+
|
|
247
|
+
// Desenha o logo se necessário
|
|
248
|
+
if (settings.showLogo && settings.logoURL) {
|
|
249
|
+
await drawLogo(ctx, settings);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Calcula as posições para o texto
|
|
253
|
+
const textX = settings.width * 0.1;
|
|
254
|
+
const textWidth = settings.width * 0.8;
|
|
255
|
+
|
|
256
|
+
// Título principal
|
|
257
|
+
ctx.font = `300 60px ${settings.font}`;
|
|
258
|
+
ctx.fillStyle = settings.textColor;
|
|
259
|
+
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 70);
|
|
260
|
+
|
|
261
|
+
// Linha de destaque
|
|
262
|
+
const lineY = settings.height * 0.5;
|
|
263
|
+
const lineWidth = settings.width * 0.2;
|
|
264
|
+
|
|
265
|
+
ctx.strokeStyle = settings.accentColor;
|
|
266
|
+
ctx.lineWidth = 3;
|
|
267
|
+
ctx.beginPath();
|
|
268
|
+
ctx.moveTo(textX, lineY);
|
|
269
|
+
ctx.lineTo(textX + lineWidth, lineY);
|
|
270
|
+
ctx.stroke();
|
|
271
|
+
|
|
272
|
+
// Subtítulo
|
|
273
|
+
if (settings.subheadline) {
|
|
274
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
275
|
+
ctx.fillStyle = settings.textColor;
|
|
276
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Botão CTA
|
|
280
|
+
const ctaWidth = 180;
|
|
281
|
+
const ctaHeight = 50;
|
|
282
|
+
const ctaX = textX;
|
|
283
|
+
const ctaY = settings.height * 0.75;
|
|
284
|
+
|
|
285
|
+
// Desenha a borda do botão
|
|
286
|
+
ctx.strokeStyle = settings.accentColor;
|
|
287
|
+
ctx.lineWidth = 2;
|
|
288
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
|
|
289
|
+
|
|
290
|
+
// Desenha o texto do botão
|
|
291
|
+
ctx.font = `bold 20px ${settings.font}`;
|
|
292
|
+
ctx.fillStyle = settings.accentColor;
|
|
293
|
+
ctx.textAlign = 'center';
|
|
294
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33);
|
|
295
|
+
ctx.textAlign = 'left';
|
|
296
|
+
|
|
297
|
+
// Se tiver imagem, desenha na metade direita
|
|
298
|
+
if (settings.imageURL) {
|
|
299
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
300
|
+
|
|
301
|
+
// Aplica filtro se necessário
|
|
302
|
+
let processedImage = image;
|
|
303
|
+
if (settings.applyFilter && settings.filterType !== 'none') {
|
|
304
|
+
processedImage = await imageFilters.applyFilter(
|
|
305
|
+
image,
|
|
306
|
+
settings.filterType
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Desenha a imagem na metade direita
|
|
311
|
+
const imageX = settings.width * 0.55;
|
|
312
|
+
const imageWidth = settings.width * 0.4;
|
|
313
|
+
const imageHeight = settings.height * 0.7;
|
|
314
|
+
const imageY = (settings.height - imageHeight) / 2;
|
|
315
|
+
|
|
316
|
+
utils.drawImageProp(ctx, processedImage, imageX, imageY, imageWidth, imageHeight);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Desenha o estilo bold do banner de anúncio
|
|
322
|
+
*
|
|
323
|
+
* @async
|
|
324
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
325
|
+
* @param {Object} settings - Configurações do banner
|
|
326
|
+
*/
|
|
327
|
+
async function drawBoldAdStyle(ctx, settings) {
|
|
328
|
+
// Desenha o fundo com cor de destaque
|
|
329
|
+
ctx.fillStyle = settings.accentColor;
|
|
330
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
331
|
+
|
|
332
|
+
// Adiciona elementos decorativos
|
|
333
|
+
for (let i = 0; i < 5; i++) {
|
|
334
|
+
const size = Math.random() * 200 + 100;
|
|
335
|
+
const x = Math.random() * settings.width;
|
|
336
|
+
const y = Math.random() * settings.height;
|
|
337
|
+
|
|
338
|
+
ctx.fillStyle = utils.hexToRgba('#FFFFFF', 0.1);
|
|
339
|
+
ctx.beginPath();
|
|
340
|
+
ctx.arc(x, y, size, 0, Math.PI * 2);
|
|
341
|
+
ctx.fill();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Se tiver imagem, desenha com sobreposição
|
|
345
|
+
if (settings.imageURL) {
|
|
346
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
347
|
+
|
|
348
|
+
// Aplica filtro se necessário
|
|
349
|
+
let processedImage = image;
|
|
350
|
+
if (settings.applyFilter && settings.filterType !== 'none') {
|
|
351
|
+
processedImage = await imageFilters.applyFilter(
|
|
352
|
+
image,
|
|
353
|
+
settings.filterType
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Desenha a imagem na metade direita
|
|
358
|
+
const imageX = settings.width * 0.5;
|
|
359
|
+
const imageWidth = settings.width * 0.5;
|
|
360
|
+
const imageHeight = settings.height;
|
|
361
|
+
|
|
362
|
+
utils.drawImageProp(ctx, processedImage, imageX, 0, imageWidth, imageHeight);
|
|
363
|
+
|
|
364
|
+
// Adiciona sobreposição gradiente
|
|
365
|
+
const gradient = ctx.createLinearGradient(imageX, 0, imageX + imageWidth / 2, 0);
|
|
366
|
+
gradient.addColorStop(0, settings.accentColor);
|
|
367
|
+
gradient.addColorStop(1, 'transparent');
|
|
368
|
+
|
|
369
|
+
ctx.fillStyle = gradient;
|
|
370
|
+
ctx.fillRect(imageX, 0, imageWidth / 2, settings.height);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Desenha o logo se necessário
|
|
374
|
+
if (settings.showLogo && settings.logoURL) {
|
|
375
|
+
await drawLogo(ctx, settings);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Calcula as posições para o texto
|
|
379
|
+
const textX = settings.width * 0.08;
|
|
380
|
+
const textWidth = settings.width * 0.4;
|
|
381
|
+
|
|
382
|
+
// Título principal
|
|
383
|
+
ctx.font = `bold 72px ${settings.font}`;
|
|
384
|
+
ctx.fillStyle = '#FFFFFF';
|
|
385
|
+
utils.wrapText(ctx, settings.headline.toUpperCase(), textX, settings.height * 0.4, textWidth, 80);
|
|
386
|
+
|
|
387
|
+
// Subtítulo
|
|
388
|
+
if (settings.subheadline) {
|
|
389
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
390
|
+
ctx.fillStyle = '#FFFFFF';
|
|
391
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.65, textWidth, 40);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Botão CTA
|
|
395
|
+
const ctaWidth = 220;
|
|
396
|
+
const ctaHeight = 70;
|
|
397
|
+
const ctaX = textX;
|
|
398
|
+
const ctaY = settings.height * 0.8;
|
|
399
|
+
|
|
400
|
+
// Desenha o fundo do botão
|
|
401
|
+
ctx.fillStyle = '#FFFFFF';
|
|
402
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
|
|
403
|
+
|
|
404
|
+
// Desenha o texto do botão
|
|
405
|
+
ctx.font = `bold 26px ${settings.font}`;
|
|
406
|
+
ctx.fillStyle = settings.accentColor;
|
|
407
|
+
ctx.textAlign = 'center';
|
|
408
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43);
|
|
409
|
+
ctx.textAlign = 'left';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Desenha o estilo corporativo do banner de anúncio
|
|
414
|
+
*
|
|
415
|
+
* @async
|
|
416
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
417
|
+
* @param {Object} settings - Configurações do banner
|
|
418
|
+
*/
|
|
419
|
+
async function drawCorporateAdStyle(ctx, settings) {
|
|
420
|
+
// Desenha o fundo
|
|
421
|
+
ctx.fillStyle = '#FFFFFF';
|
|
422
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
423
|
+
|
|
424
|
+
// Adiciona uma barra lateral com a cor de destaque
|
|
425
|
+
const sidebarWidth = settings.width * 0.05;
|
|
426
|
+
ctx.fillStyle = settings.accentColor;
|
|
427
|
+
ctx.fillRect(0, 0, sidebarWidth, settings.height);
|
|
428
|
+
|
|
429
|
+
// Se tiver imagem, desenha com sobreposição se necessário
|
|
430
|
+
if (settings.imageURL) {
|
|
431
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
432
|
+
|
|
433
|
+
// Aplica filtro se necessário
|
|
434
|
+
let processedImage = image;
|
|
435
|
+
if (settings.applyFilter && settings.filterType !== 'none') {
|
|
436
|
+
processedImage = await imageFilters.applyFilter(
|
|
437
|
+
image,
|
|
438
|
+
settings.filterType
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Desenha a imagem como fundo com baixa opacidade
|
|
443
|
+
ctx.globalAlpha = 0.1;
|
|
444
|
+
utils.drawImageProp(ctx, processedImage, 0, 0, settings.width, settings.height);
|
|
445
|
+
ctx.globalAlpha = 1.0;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Desenha o logo se necessário
|
|
449
|
+
if (settings.showLogo && settings.logoURL) {
|
|
450
|
+
// Força a posição do logo para o canto superior direito
|
|
451
|
+
const originalPosition = settings.logoPosition;
|
|
452
|
+
settings.logoPosition = 'top-right';
|
|
453
|
+
await drawLogo(ctx, settings);
|
|
454
|
+
settings.logoPosition = originalPosition;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Calcula as posições para o texto
|
|
458
|
+
const textX = sidebarWidth + settings.width * 0.05;
|
|
459
|
+
const textWidth = settings.width * 0.7;
|
|
460
|
+
|
|
461
|
+
// Título principal
|
|
462
|
+
ctx.font = `bold 54px ${settings.font}`;
|
|
463
|
+
ctx.fillStyle = settings.textColor;
|
|
464
|
+
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 60);
|
|
465
|
+
|
|
466
|
+
// Linha de destaque
|
|
467
|
+
const lineY = settings.height * 0.5;
|
|
468
|
+
const lineWidth = settings.width * 0.2;
|
|
469
|
+
|
|
470
|
+
ctx.strokeStyle = settings.accentColor;
|
|
471
|
+
ctx.lineWidth = 4;
|
|
472
|
+
ctx.beginPath();
|
|
473
|
+
ctx.moveTo(textX, lineY);
|
|
474
|
+
ctx.lineTo(textX + lineWidth, lineY);
|
|
475
|
+
ctx.stroke();
|
|
476
|
+
|
|
477
|
+
// Subtítulo
|
|
478
|
+
if (settings.subheadline) {
|
|
479
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
480
|
+
ctx.fillStyle = settings.textColor;
|
|
481
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Botão CTA
|
|
485
|
+
const ctaWidth = 220;
|
|
486
|
+
const ctaHeight = 60;
|
|
487
|
+
const ctaX = textX;
|
|
488
|
+
const ctaY = settings.height * 0.75;
|
|
489
|
+
|
|
490
|
+
// Desenha o fundo do botão
|
|
491
|
+
ctx.fillStyle = settings.accentColor;
|
|
492
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, true);
|
|
493
|
+
|
|
494
|
+
// Desenha o texto do botão
|
|
495
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
496
|
+
ctx.fillStyle = '#FFFFFF';
|
|
497
|
+
ctx.textAlign = 'center';
|
|
498
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
499
|
+
ctx.textAlign = 'left';
|
|
500
|
+
|
|
501
|
+
// Desenha borda se necessário
|
|
502
|
+
if (settings.showBorder) {
|
|
503
|
+
ctx.strokeStyle = settings.borderColor;
|
|
504
|
+
ctx.lineWidth = settings.borderWidth;
|
|
505
|
+
utils.roundRect(ctx, settings.borderWidth / 2, settings.borderWidth / 2,
|
|
506
|
+
settings.width - settings.borderWidth, settings.height - settings.borderWidth,
|
|
507
|
+
0, false, true);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Desenha o logo na posição especificada
|
|
513
|
+
*
|
|
514
|
+
* @async
|
|
515
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
516
|
+
* @param {Object} settings - Configurações do banner
|
|
517
|
+
*/
|
|
518
|
+
async function drawLogo(ctx, settings) {
|
|
519
|
+
if (!settings.logoURL) return;
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
const logo = await utils.loadImage(settings.logoURL);
|
|
523
|
+
const padding = 20;
|
|
524
|
+
let x, y;
|
|
525
|
+
|
|
526
|
+
// Determina a posição do logo
|
|
527
|
+
switch (settings.logoPosition) {
|
|
528
|
+
case 'top-right':
|
|
529
|
+
x = settings.width - settings.logoSize - padding;
|
|
530
|
+
y = padding;
|
|
531
|
+
break;
|
|
532
|
+
case 'bottom-left':
|
|
533
|
+
x = padding;
|
|
534
|
+
y = settings.height - settings.logoSize - padding;
|
|
535
|
+
break;
|
|
536
|
+
case 'bottom-right':
|
|
537
|
+
x = settings.width - settings.logoSize - padding;
|
|
538
|
+
y = settings.height - settings.logoSize - padding;
|
|
539
|
+
break;
|
|
540
|
+
case 'top-left':
|
|
541
|
+
default:
|
|
542
|
+
x = padding;
|
|
543
|
+
y = padding;
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Desenha o logo
|
|
548
|
+
utils.drawImageProp(ctx, logo, x, y, settings.logoSize, settings.logoSize);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.error('Erro ao carregar o logo:', error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Cria um banner para landing page
|
|
556
|
+
*
|
|
557
|
+
* @async
|
|
558
|
+
* @param {Object} options - Opções de configuração
|
|
559
|
+
* @param {string} options.headline - Título principal da landing page
|
|
560
|
+
* @param {string} [options.subheadline] - Subtítulo da landing page
|
|
561
|
+
* @param {string} [options.imageURL] - URL da imagem principal
|
|
562
|
+
* @param {string} [options.ctaText="COMECE AGORA"] - Texto do botão de call-to-action
|
|
563
|
+
* @param {string} [options.ctaURL] - URL para o botão de call-to-action
|
|
564
|
+
* @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
|
|
565
|
+
* @param {string} [options.textColor="#333333"] - Cor do texto
|
|
566
|
+
* @param {string} [options.accentColor="#4A90E2"] - Cor de destaque
|
|
567
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "hero", "split", "video")
|
|
568
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
569
|
+
* @param {number} [options.height=628] - Altura do banner em pixels
|
|
570
|
+
* @param {string} [options.font="Poppins"] - Nome da fonte
|
|
571
|
+
* @param {boolean} [options.showLogo=true] - Se deve mostrar o logo
|
|
572
|
+
* @param {string} [options.logoURL] - URL do logo da empresa
|
|
573
|
+
* @param {Array<string>} [options.features=[]] - Lista de recursos/benefícios
|
|
574
|
+
* @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem
|
|
575
|
+
* @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição
|
|
576
|
+
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
|
|
577
|
+
* @param {string} [options.backgroundPattern="none"] - Padrão de fundo ("none", "dots", "lines", "grid")
|
|
578
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
579
|
+
*/
|
|
580
|
+
async function createLandingPageBanner(options) {
|
|
581
|
+
// Validação de parâmetros
|
|
582
|
+
validator.validateRequiredParams(options, ['headline']);
|
|
583
|
+
|
|
584
|
+
// Valores padrão
|
|
585
|
+
const defaults = {
|
|
586
|
+
subheadline: '',
|
|
587
|
+
imageURL: '',
|
|
588
|
+
ctaText: 'COMECE AGORA',
|
|
589
|
+
ctaURL: '',
|
|
590
|
+
backgroundColor: '#FFFFFF',
|
|
591
|
+
textColor: '#333333',
|
|
592
|
+
accentColor: '#4A90E2',
|
|
593
|
+
style: 'standard',
|
|
594
|
+
width: 1200,
|
|
595
|
+
height: 628,
|
|
596
|
+
font: 'Poppins',
|
|
597
|
+
showLogo: true,
|
|
598
|
+
logoURL: '',
|
|
599
|
+
features: [],
|
|
600
|
+
showOverlay: true,
|
|
601
|
+
overlayOpacity: 0.5,
|
|
602
|
+
overlayColor: '#000000',
|
|
603
|
+
backgroundPattern: 'none'
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// Mescla as opções com os valores padrão
|
|
607
|
+
const settings = { ...defaults, ...options };
|
|
608
|
+
|
|
609
|
+
// Cria a imagem
|
|
610
|
+
const img = PImage.make(settings.width, settings.height);
|
|
611
|
+
const ctx = img.getContext('2d');
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
615
|
+
switch (settings.style) {
|
|
616
|
+
case 'hero':
|
|
617
|
+
await drawHeroLandingStyle(ctx, settings);
|
|
618
|
+
break;
|
|
619
|
+
case 'split':
|
|
620
|
+
await drawSplitLandingStyle(ctx, settings);
|
|
621
|
+
break;
|
|
622
|
+
case 'video':
|
|
623
|
+
await drawVideoLandingStyle(ctx, settings);
|
|
624
|
+
break;
|
|
625
|
+
case 'standard':
|
|
626
|
+
default:
|
|
627
|
+
await drawStandardLandingStyle(ctx, settings);
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Retorna o buffer da imagem
|
|
632
|
+
return await utils.getBufferFromImage(img);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.error('Erro ao criar banner de landing page:', error);
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Desenha o estilo padrão do banner de landing page
|
|
641
|
+
*
|
|
642
|
+
* @async
|
|
643
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
644
|
+
* @param {Object} settings - Configurações do banner
|
|
645
|
+
*/
|
|
646
|
+
async function drawStandardLandingStyle(ctx, settings) {
|
|
647
|
+
// Desenha o fundo
|
|
648
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
649
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
650
|
+
|
|
651
|
+
// Adiciona padrão de fundo se necessário
|
|
652
|
+
if (settings.backgroundPattern !== 'none') {
|
|
653
|
+
drawBackgroundPattern(ctx, settings);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Desenha o logo se necessário
|
|
657
|
+
if (settings.showLogo && settings.logoURL) {
|
|
658
|
+
const logo = await utils.loadImage(settings.logoURL);
|
|
659
|
+
const logoSize = 60;
|
|
660
|
+
const padding = 20;
|
|
661
|
+
|
|
662
|
+
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Calcula as posições para o texto
|
|
666
|
+
const textX = settings.width * 0.1;
|
|
667
|
+
const textWidth = settings.width * 0.8;
|
|
668
|
+
|
|
669
|
+
// Título principal
|
|
670
|
+
ctx.font = `bold 64px ${settings.font}`;
|
|
671
|
+
ctx.fillStyle = settings.textColor;
|
|
672
|
+
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.35, textWidth, 70);
|
|
673
|
+
|
|
674
|
+
// Subtítulo
|
|
675
|
+
if (settings.subheadline) {
|
|
676
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
677
|
+
ctx.fillStyle = settings.textColor;
|
|
678
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.5, textWidth, 40);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Features/Benefícios
|
|
682
|
+
if (settings.features.length > 0) {
|
|
683
|
+
const featureY = settings.height * 0.6;
|
|
684
|
+
const featureSpacing = 40;
|
|
685
|
+
|
|
686
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
687
|
+
ctx.fillStyle = settings.textColor;
|
|
688
|
+
|
|
689
|
+
for (let i = 0; i < Math.min(settings.features.length, 3); i++) {
|
|
690
|
+
const y = featureY + i * featureSpacing;
|
|
691
|
+
|
|
692
|
+
// Desenha um ícone de check
|
|
693
|
+
ctx.fillStyle = settings.accentColor;
|
|
694
|
+
ctx.beginPath();
|
|
695
|
+
ctx.arc(textX + 10, y - 8, 10, 0, Math.PI * 2);
|
|
696
|
+
ctx.fill();
|
|
697
|
+
|
|
698
|
+
ctx.fillStyle = '#FFFFFF';
|
|
699
|
+
ctx.font = `bold 16px ${settings.font}`;
|
|
700
|
+
ctx.fillText('✓', textX + 5, y - 3);
|
|
701
|
+
|
|
702
|
+
// Desenha o texto do recurso
|
|
703
|
+
ctx.fillStyle = settings.textColor;
|
|
704
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
705
|
+
ctx.fillText(settings.features[i], textX + 30, y);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Botão CTA
|
|
710
|
+
const ctaWidth = 250;
|
|
711
|
+
const ctaHeight = 60;
|
|
712
|
+
const ctaX = textX;
|
|
713
|
+
const ctaY = settings.height * 0.8;
|
|
714
|
+
|
|
715
|
+
// Desenha o fundo do botão
|
|
716
|
+
ctx.fillStyle = settings.accentColor;
|
|
717
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true);
|
|
718
|
+
|
|
719
|
+
// Desenha o texto do botão
|
|
720
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
721
|
+
ctx.fillStyle = '#FFFFFF';
|
|
722
|
+
ctx.textAlign = 'center';
|
|
723
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
724
|
+
ctx.textAlign = 'left';
|
|
725
|
+
|
|
726
|
+
// Se tiver imagem, desenha na metade direita
|
|
727
|
+
if (settings.imageURL) {
|
|
728
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
729
|
+
|
|
730
|
+
// Desenha a imagem na metade direita
|
|
731
|
+
const imageX = settings.width * 0.6;
|
|
732
|
+
const imageWidth = settings.width * 0.35;
|
|
733
|
+
const imageHeight = settings.height * 0.7;
|
|
734
|
+
const imageY = (settings.height - imageHeight) / 2;
|
|
735
|
+
|
|
736
|
+
utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Desenha o estilo hero do banner de landing page
|
|
742
|
+
*
|
|
743
|
+
* @async
|
|
744
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
745
|
+
* @param {Object} settings - Configurações do banner
|
|
746
|
+
*/
|
|
747
|
+
async function drawHeroLandingStyle(ctx, settings) {
|
|
748
|
+
// Se tiver imagem, desenha como fundo
|
|
749
|
+
if (settings.imageURL) {
|
|
750
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
751
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
752
|
+
|
|
753
|
+
// Adiciona sobreposição se necessário
|
|
754
|
+
if (settings.showOverlay) {
|
|
755
|
+
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity);
|
|
756
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
// Desenha o fundo com gradiente
|
|
760
|
+
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
|
|
761
|
+
gradient.addColorStop(0, settings.accentColor);
|
|
762
|
+
gradient.addColorStop(1, utils.adjustColor(settings.accentColor, -0.3));
|
|
763
|
+
|
|
764
|
+
ctx.fillStyle = gradient;
|
|
765
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Desenha o logo se necessário
|
|
769
|
+
if (settings.showLogo && settings.logoURL) {
|
|
770
|
+
const logo = await utils.loadImage(settings.logoURL);
|
|
771
|
+
const logoSize = 60;
|
|
772
|
+
const padding = 20;
|
|
773
|
+
|
|
774
|
+
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Desenha o conteúdo de texto centralizado
|
|
778
|
+
ctx.textAlign = 'center';
|
|
779
|
+
|
|
780
|
+
// Título principal
|
|
781
|
+
ctx.font = `bold 72px ${settings.font}`;
|
|
782
|
+
ctx.fillStyle = '#FFFFFF';
|
|
783
|
+
utils.wrapTextCentered(ctx, settings.headline, settings.width / 2, settings.height * 0.4, settings.width * 0.8, 80);
|
|
784
|
+
|
|
785
|
+
// Subtítulo
|
|
786
|
+
if (settings.subheadline) {
|
|
787
|
+
ctx.font = `300 36px ${settings.font}`;
|
|
788
|
+
ctx.fillStyle = '#FFFFFF';
|
|
789
|
+
utils.wrapTextCentered(ctx, settings.subheadline, settings.width / 2, settings.height * 0.6, settings.width * 0.7, 50);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Botão CTA
|
|
793
|
+
const ctaWidth = 300;
|
|
794
|
+
const ctaHeight = 70;
|
|
795
|
+
const ctaX = (settings.width - ctaWidth) / 2;
|
|
796
|
+
const ctaY = settings.height * 0.75;
|
|
797
|
+
|
|
798
|
+
// Desenha o fundo do botão
|
|
799
|
+
ctx.fillStyle = '#FFFFFF';
|
|
800
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
|
|
801
|
+
|
|
802
|
+
// Desenha o texto do botão
|
|
803
|
+
ctx.font = `bold 28px ${settings.font}`;
|
|
804
|
+
ctx.fillStyle = settings.accentColor;
|
|
805
|
+
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
|
|
806
|
+
|
|
807
|
+
// Restaura o alinhamento de texto
|
|
808
|
+
ctx.textAlign = 'left';
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Desenha o estilo split do banner de landing page
|
|
813
|
+
*
|
|
814
|
+
* @async
|
|
815
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
816
|
+
* @param {Object} settings - Configurações do banner
|
|
817
|
+
*/
|
|
818
|
+
async function drawSplitLandingStyle(ctx, settings) {
|
|
819
|
+
// Desenha o fundo dividido
|
|
820
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
821
|
+
ctx.fillRect(0, 0, settings.width / 2, settings.height);
|
|
822
|
+
|
|
823
|
+
ctx.fillStyle = settings.accentColor;
|
|
824
|
+
ctx.fillRect(settings.width / 2, 0, settings.width / 2, settings.height);
|
|
825
|
+
|
|
826
|
+
// Adiciona padrão de fundo se necessário
|
|
827
|
+
if (settings.backgroundPattern !== 'none') {
|
|
828
|
+
// Padrão apenas na metade esquerda
|
|
829
|
+
const originalPattern = settings.backgroundPattern;
|
|
830
|
+
const originalWidth = settings.width;
|
|
831
|
+
|
|
832
|
+
settings.width = settings.width / 2;
|
|
833
|
+
drawBackgroundPattern(ctx, settings);
|
|
834
|
+
|
|
835
|
+
settings.backgroundPattern = originalPattern;
|
|
836
|
+
settings.width = originalWidth;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Desenha o logo se necessário
|
|
840
|
+
if (settings.showLogo && settings.logoURL) {
|
|
841
|
+
const logo = await utils.loadImage(settings.logoURL);
|
|
842
|
+
const logoSize = 60;
|
|
843
|
+
const padding = 20;
|
|
844
|
+
|
|
845
|
+
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Calcula as posições para o texto na metade esquerda
|
|
849
|
+
const textX = settings.width * 0.05;
|
|
850
|
+
const textWidth = settings.width * 0.4;
|
|
851
|
+
|
|
852
|
+
// Título principal
|
|
853
|
+
ctx.font = `bold 54px ${settings.font}`;
|
|
854
|
+
ctx.fillStyle = settings.textColor;
|
|
855
|
+
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.35, textWidth, 60);
|
|
856
|
+
|
|
857
|
+
// Subtítulo
|
|
858
|
+
if (settings.subheadline) {
|
|
859
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
860
|
+
ctx.fillStyle = settings.textColor;
|
|
861
|
+
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.5, textWidth, 40);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Features/Benefícios
|
|
865
|
+
if (settings.features.length > 0) {
|
|
866
|
+
const featureY = settings.height * 0.6;
|
|
867
|
+
const featureSpacing = 40;
|
|
868
|
+
|
|
869
|
+
ctx.font = `300 22px ${settings.font}`;
|
|
870
|
+
ctx.fillStyle = settings.textColor;
|
|
871
|
+
|
|
872
|
+
for (let i = 0; i < Math.min(settings.features.length, 3); i++) {
|
|
873
|
+
const y = featureY + i * featureSpacing;
|
|
874
|
+
|
|
875
|
+
// Desenha um ícone de check
|
|
876
|
+
ctx.fillStyle = settings.accentColor;
|
|
877
|
+
ctx.beginPath();
|
|
878
|
+
ctx.arc(textX + 10, y - 8, 10, 0, Math.PI * 2);
|
|
879
|
+
ctx.fill();
|
|
880
|
+
|
|
881
|
+
ctx.fillStyle = '#FFFFFF';
|
|
882
|
+
ctx.font = `bold 16px ${settings.font}`;
|
|
883
|
+
ctx.fillText('✓', textX + 5, y - 3);
|
|
884
|
+
|
|
885
|
+
// Desenha o texto do recurso
|
|
886
|
+
ctx.fillStyle = settings.textColor;
|
|
887
|
+
ctx.font = `300 22px ${settings.font}`;
|
|
888
|
+
ctx.fillText(settings.features[i], textX + 30, y);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Botão CTA
|
|
893
|
+
const ctaWidth = 220;
|
|
894
|
+
const ctaHeight = 60;
|
|
895
|
+
const ctaX = textX;
|
|
896
|
+
const ctaY = settings.height * 0.8;
|
|
897
|
+
|
|
898
|
+
// Desenha o fundo do botão
|
|
899
|
+
ctx.fillStyle = settings.accentColor;
|
|
900
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true);
|
|
901
|
+
|
|
902
|
+
// Desenha o texto do botão
|
|
903
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
904
|
+
ctx.fillStyle = '#FFFFFF';
|
|
905
|
+
ctx.textAlign = 'center';
|
|
906
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
907
|
+
ctx.textAlign = 'left';
|
|
908
|
+
|
|
909
|
+
// Se tiver imagem, desenha na metade direita
|
|
910
|
+
if (settings.imageURL) {
|
|
911
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
912
|
+
|
|
913
|
+
// Desenha a imagem na metade direita
|
|
914
|
+
const imageX = settings.width * 0.55;
|
|
915
|
+
const imageWidth = settings.width * 0.4;
|
|
916
|
+
const imageHeight = settings.height * 0.7;
|
|
917
|
+
const imageY = (settings.height - imageHeight) / 2;
|
|
918
|
+
|
|
919
|
+
utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Desenha o estilo video do banner de landing page
|
|
925
|
+
*
|
|
926
|
+
* @async
|
|
927
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
928
|
+
* @param {Object} settings - Configurações do banner
|
|
929
|
+
*/
|
|
930
|
+
async function drawVideoLandingStyle(ctx, settings) {
|
|
931
|
+
// Desenha o fundo
|
|
932
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
933
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
934
|
+
|
|
935
|
+
// Se tiver imagem, desenha como thumbnail de vídeo
|
|
936
|
+
if (settings.imageURL) {
|
|
937
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
938
|
+
|
|
939
|
+
// Desenha a imagem como thumbnail de vídeo
|
|
940
|
+
const videoWidth = settings.width * 0.9;
|
|
941
|
+
const videoHeight = settings.height * 0.5;
|
|
942
|
+
const videoX = (settings.width - videoWidth) / 2;
|
|
943
|
+
const videoY = settings.height * 0.1;
|
|
944
|
+
|
|
945
|
+
utils.drawImageProp(ctx, image, videoX, videoY, videoWidth, videoHeight);
|
|
946
|
+
|
|
947
|
+
// Adiciona sobreposição se necessário
|
|
948
|
+
if (settings.showOverlay) {
|
|
949
|
+
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity / 2);
|
|
950
|
+
ctx.fillRect(videoX, videoY, videoWidth, videoHeight);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Adiciona botão de play
|
|
954
|
+
const playSize = 80;
|
|
955
|
+
const playX = videoX + videoWidth / 2 - playSize / 2;
|
|
956
|
+
const playY = videoY + videoHeight / 2 - playSize / 2;
|
|
957
|
+
|
|
958
|
+
// Círculo de fundo
|
|
959
|
+
ctx.fillStyle = utils.hexToRgba('#000000', 0.7);
|
|
960
|
+
ctx.beginPath();
|
|
961
|
+
ctx.arc(playX + playSize / 2, playY + playSize / 2, playSize / 2, 0, Math.PI * 2);
|
|
962
|
+
ctx.fill();
|
|
963
|
+
|
|
964
|
+
// Triângulo de play
|
|
965
|
+
ctx.fillStyle = '#FFFFFF';
|
|
966
|
+
ctx.beginPath();
|
|
967
|
+
ctx.moveTo(playX + playSize * 0.35, playY + playSize * 0.25);
|
|
968
|
+
ctx.lineTo(playX + playSize * 0.35, playY + playSize * 0.75);
|
|
969
|
+
ctx.lineTo(playX + playSize * 0.75, playY + playSize * 0.5);
|
|
970
|
+
ctx.closePath();
|
|
971
|
+
ctx.fill();
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Desenha o logo se necessário
|
|
975
|
+
if (settings.showLogo && settings.logoURL) {
|
|
976
|
+
const logo = await utils.loadImage(settings.logoURL);
|
|
977
|
+
const logoSize = 60;
|
|
978
|
+
const padding = 20;
|
|
979
|
+
|
|
980
|
+
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Desenha o conteúdo de texto centralizado
|
|
984
|
+
ctx.textAlign = 'center';
|
|
985
|
+
|
|
986
|
+
// Título principal
|
|
987
|
+
ctx.font = `bold 54px ${settings.font}`;
|
|
988
|
+
ctx.fillStyle = settings.textColor;
|
|
989
|
+
utils.wrapTextCentered(ctx, settings.headline, settings.width / 2, settings.height * 0.7, settings.width * 0.8, 60);
|
|
990
|
+
|
|
991
|
+
// Subtítulo
|
|
992
|
+
if (settings.subheadline) {
|
|
993
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
994
|
+
ctx.fillStyle = settings.textColor;
|
|
995
|
+
utils.wrapTextCentered(ctx, settings.subheadline, settings.width / 2, settings.height * 0.8, settings.width * 0.7, 40);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Botão CTA
|
|
999
|
+
const ctaWidth = 250;
|
|
1000
|
+
const ctaHeight = 60;
|
|
1001
|
+
const ctaX = (settings.width - ctaWidth) / 2;
|
|
1002
|
+
const ctaY = settings.height * 0.88;
|
|
1003
|
+
|
|
1004
|
+
// Desenha o fundo do botão
|
|
1005
|
+
ctx.fillStyle = settings.accentColor;
|
|
1006
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true);
|
|
1007
|
+
|
|
1008
|
+
// Desenha o texto do botão
|
|
1009
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
1010
|
+
ctx.fillStyle = '#FFFFFF';
|
|
1011
|
+
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 38);
|
|
1012
|
+
|
|
1013
|
+
// Restaura o alinhamento de texto
|
|
1014
|
+
ctx.textAlign = 'left';
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Desenha um padrão de fundo
|
|
1019
|
+
*
|
|
1020
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1021
|
+
* @param {Object} settings - Configurações do banner
|
|
1022
|
+
*/
|
|
1023
|
+
function drawBackgroundPattern(ctx, settings) {
|
|
1024
|
+
const patternColor = utils.hexToRgba(settings.textColor, 0.05);
|
|
1025
|
+
ctx.fillStyle = patternColor;
|
|
1026
|
+
|
|
1027
|
+
switch (settings.backgroundPattern) {
|
|
1028
|
+
case 'dots':
|
|
1029
|
+
// Desenha um padrão de pontos
|
|
1030
|
+
const dotSpacing = 30;
|
|
1031
|
+
const dotSize = 3;
|
|
1032
|
+
|
|
1033
|
+
for (let x = dotSpacing; x < settings.width; x += dotSpacing) {
|
|
1034
|
+
for (let y = dotSpacing; y < settings.height; y += dotSpacing) {
|
|
1035
|
+
ctx.beginPath();
|
|
1036
|
+
ctx.arc(x, y, dotSize, 0, Math.PI * 2);
|
|
1037
|
+
ctx.fill();
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
break;
|
|
1041
|
+
|
|
1042
|
+
case 'lines':
|
|
1043
|
+
// Desenha um padrão de linhas
|
|
1044
|
+
const lineSpacing = 40;
|
|
1045
|
+
ctx.lineWidth = 1;
|
|
1046
|
+
ctx.strokeStyle = patternColor;
|
|
1047
|
+
|
|
1048
|
+
for (let y = lineSpacing; y < settings.height; y += lineSpacing) {
|
|
1049
|
+
ctx.beginPath();
|
|
1050
|
+
ctx.moveTo(0, y);
|
|
1051
|
+
ctx.lineTo(settings.width, y);
|
|
1052
|
+
ctx.stroke();
|
|
1053
|
+
}
|
|
1054
|
+
break;
|
|
1055
|
+
|
|
1056
|
+
case 'grid':
|
|
1057
|
+
// Desenha um padrão de grade
|
|
1058
|
+
const gridSpacing = 40;
|
|
1059
|
+
ctx.lineWidth = 1;
|
|
1060
|
+
ctx.strokeStyle = patternColor;
|
|
1061
|
+
|
|
1062
|
+
// Linhas horizontais
|
|
1063
|
+
for (let y = gridSpacing; y < settings.height; y += gridSpacing) {
|
|
1064
|
+
ctx.beginPath();
|
|
1065
|
+
ctx.moveTo(0, y);
|
|
1066
|
+
ctx.lineTo(settings.width, y);
|
|
1067
|
+
ctx.stroke();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Linhas verticais
|
|
1071
|
+
for (let x = gridSpacing; x < settings.width; x += gridSpacing) {
|
|
1072
|
+
ctx.beginPath();
|
|
1073
|
+
ctx.moveTo(x, 0);
|
|
1074
|
+
ctx.lineTo(x, settings.height);
|
|
1075
|
+
ctx.stroke();
|
|
1076
|
+
}
|
|
1077
|
+
break;
|
|
1078
|
+
|
|
1079
|
+
default:
|
|
1080
|
+
// Nenhum padrão
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
module.exports = {
|
|
1086
|
+
createAdvertisementBanner,
|
|
1087
|
+
createLandingPageBanner
|
|
1088
|
+
};
|
|
1089
|
+
|