@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.
Files changed (48) hide show
  1. package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
  2. package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
  3. package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
  4. package/assets/fonts/Others/ChirpRegular.ttf +0 -0
  5. package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
  6. package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
  7. package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
  8. package/assets/placeholders/album_art.png +0 -0
  9. package/assets/placeholders/avatar.png +0 -0
  10. package/assets/placeholders/badge.jpg +0 -0
  11. package/assets/placeholders/badge.png +0 -0
  12. package/assets/placeholders/badge_2.jpg +0 -0
  13. package/assets/placeholders/badge_3.jpg +0 -0
  14. package/assets/placeholders/badge_4.jpg +0 -0
  15. package/assets/placeholders/badge_5.jpg +0 -0
  16. package/assets/placeholders/banner.jpeg +0 -0
  17. package/assets/placeholders/images.jpeg +0 -0
  18. package/index.js +153 -0
  19. package/package.json +34 -0
  20. package/src/animation-effects.js +631 -0
  21. package/src/cache-manager.js +258 -0
  22. package/src/community-banner.js +1536 -0
  23. package/src/constants.js +208 -0
  24. package/src/discord-profile.js +584 -0
  25. package/src/e-commerce-banner.js +1214 -0
  26. package/src/effects.js +355 -0
  27. package/src/error-handler.js +305 -0
  28. package/src/event-banner.js +1319 -0
  29. package/src/facebook-post.js +679 -0
  30. package/src/gradient-welcome.js +430 -0
  31. package/src/image-filters.js +1034 -0
  32. package/src/image-processor.js +1014 -0
  33. package/src/instagram-post.js +504 -0
  34. package/src/interactive-elements.js +1208 -0
  35. package/src/linkedin-post.js +658 -0
  36. package/src/marketing-banner.js +1089 -0
  37. package/src/minimalist-banner.js +892 -0
  38. package/src/modern-profile.js +755 -0
  39. package/src/performance-optimizer.js +216 -0
  40. package/src/telegram-header.js +544 -0
  41. package/src/test-runner.js +645 -0
  42. package/src/tiktok-post.js +713 -0
  43. package/src/twitter-header.js +604 -0
  44. package/src/validator.js +442 -0
  45. package/src/welcome-leave.js +445 -0
  46. package/src/whatsapp-status.js +386 -0
  47. package/src/youtube-thumbnail.js +681 -0
  48. 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
+