@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,1214 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Módulo de banners para E-commerce
5
+ *
6
+ * Este módulo fornece funções para criar banners de e-commerce
7
+ * com diferentes estilos e opções de personalização.
8
+ *
9
+ * @module e-commerce-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 de produto para e-commerce
26
+ *
27
+ * @async
28
+ * @param {Object} options - Opções de configuração
29
+ * @param {string} options.productName - Nome do produto
30
+ * @param {string} options.productImageURL - URL da imagem do produto
31
+ * @param {string} [options.price] - Preço do produto
32
+ * @param {string} [options.currency="R$"] - Símbolo da moeda
33
+ * @param {string} [options.discountBadge] - Texto do badge de desconto (ex: "20% OFF")
34
+ * @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action
35
+ * @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
36
+ * @param {string} [options.accentColor="#FF6B6B"] - Cor de destaque
37
+ * @param {string} [options.textColor="#333333"] - Cor do texto
38
+ * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "gradient")
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.showRating=false] - Se deve mostrar classificação por estrelas
43
+ * @param {number} [options.rating=5] - Classificação do produto (1-5)
44
+ * @param {boolean} [options.showBorder=true] - Se deve mostrar borda
45
+ * @param {string} [options.borderColor="#EEEEEE"] - Cor da borda
46
+ * @param {number} [options.borderWidth=2] - Largura da borda em pixels
47
+ * @param {number} [options.borderRadius=10] - Raio da borda em pixels
48
+ * @param {boolean} [options.showShadow=true] - Se deve mostrar sombra
49
+ * @param {string} [options.shadowColor="rgba(0,0,0,0.1)"] - Cor da sombra
50
+ * @param {Array<string>} [options.tags=[]] - Tags do produto
51
+ * @param {string} [options.backgroundImageURL] - URL da imagem de fundo
52
+ * @param {number} [options.backgroundOpacity=0.1] - Opacidade da imagem de fundo
53
+ * @param {boolean} [options.applyFilter=false] - Se deve aplicar filtro à imagem do produto
54
+ * @param {string} [options.filterType="none"] - Tipo de filtro ("none", "grayscale", "sepia", "blur", etc.)
55
+ * @returns {Promise<Buffer>} - Buffer da imagem gerada
56
+ */
57
+ async function createProductBanner(options) {
58
+ // Validação de parâmetros
59
+ validator.validateRequiredParams(options, ['productName', 'productImageURL']);
60
+
61
+ // Valores padrão
62
+ const defaults = {
63
+ price: '',
64
+ currency: 'R$',
65
+ discountBadge: '',
66
+ ctaText: 'COMPRAR AGORA',
67
+ backgroundColor: '#FFFFFF',
68
+ accentColor: '#FF6B6B',
69
+ textColor: '#333333',
70
+ style: 'standard',
71
+ width: 1200,
72
+ height: 628,
73
+ font: 'Poppins',
74
+ showRating: false,
75
+ rating: 5,
76
+ showBorder: true,
77
+ borderColor: '#EEEEEE',
78
+ borderWidth: 2,
79
+ borderRadius: 10,
80
+ showShadow: true,
81
+ shadowColor: 'rgba(0,0,0,0.1)',
82
+ tags: [],
83
+ backgroundImageURL: '',
84
+ backgroundOpacity: 0.1,
85
+ applyFilter: false,
86
+ filterType: 'none'
87
+ };
88
+
89
+ // Mescla as opções com os valores padrão
90
+ const settings = { ...defaults, ...options };
91
+
92
+ // Cria a imagem
93
+ const img = PImage.make(settings.width, settings.height);
94
+ const ctx = img.getContext('2d');
95
+
96
+ try {
97
+ // Aplica o estilo de acordo com a opção selecionada
98
+ switch (settings.style) {
99
+ case 'minimal':
100
+ await drawMinimalStyle(ctx, settings);
101
+ break;
102
+ case 'bold':
103
+ await drawBoldStyle(ctx, settings);
104
+ break;
105
+ case 'gradient':
106
+ await drawGradientStyle(ctx, settings);
107
+ break;
108
+ case 'standard':
109
+ default:
110
+ await drawStandardStyle(ctx, settings);
111
+ break;
112
+ }
113
+
114
+ // Retorna o buffer da imagem
115
+ return await utils.getBufferFromImage(img);
116
+ } catch (error) {
117
+ console.error('Erro ao criar banner de produto:', error);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Desenha o estilo padrão do banner de produto
124
+ *
125
+ * @async
126
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
127
+ * @param {Object} settings - Configurações do banner
128
+ */
129
+ async function drawStandardStyle(ctx, settings) {
130
+ // Desenha o fundo
131
+ ctx.fillStyle = settings.backgroundColor;
132
+ ctx.fillRect(0, 0, settings.width, settings.height);
133
+
134
+ // Se tiver imagem de fundo, desenha com opacidade
135
+ if (settings.backgroundImageURL) {
136
+ const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
137
+ ctx.globalAlpha = settings.backgroundOpacity;
138
+ ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
139
+ ctx.globalAlpha = 1.0;
140
+ }
141
+
142
+ // Carrega a imagem do produto
143
+ let productImage = await utils.loadImage(settings.productImageURL);
144
+
145
+ // Aplica filtro se necessário
146
+ if (settings.applyFilter && settings.filterType !== 'none') {
147
+ productImage = await imageFilters.applyFilter(
148
+ productImage,
149
+ settings.filterType
150
+ );
151
+ }
152
+
153
+ // Calcula as dimensões e posições
154
+ const productWidth = settings.width * 0.4;
155
+ const productHeight = settings.height * 0.7;
156
+ const productX = settings.width * 0.1;
157
+ const productY = (settings.height - productHeight) / 2;
158
+
159
+ // Desenha a imagem do produto
160
+ utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
161
+
162
+ // Desenha o conteúdo de texto
163
+ const textX = productX + productWidth + 50;
164
+ const textWidth = settings.width - textX - 50;
165
+
166
+ // Nome do produto
167
+ ctx.font = `bold 48px ${settings.font}`;
168
+ ctx.fillStyle = settings.textColor;
169
+ utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60);
170
+
171
+ // Preço
172
+ if (settings.price) {
173
+ ctx.font = `bold 64px ${settings.font}`;
174
+ ctx.fillStyle = settings.accentColor;
175
+ ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
176
+ }
177
+
178
+ // Badge de desconto
179
+ if (settings.discountBadge) {
180
+ const badgeWidth = 120;
181
+ const badgeHeight = 40;
182
+ const badgeX = textX;
183
+ const badgeY = settings.height * 0.55;
184
+
185
+ // Desenha o fundo do badge
186
+ ctx.fillStyle = settings.accentColor;
187
+ utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 5, true);
188
+
189
+ // Desenha o texto do badge
190
+ ctx.font = `bold 20px ${settings.font}`;
191
+ ctx.fillStyle = '#FFFFFF';
192
+ ctx.textAlign = 'center';
193
+ ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 28);
194
+ ctx.textAlign = 'left';
195
+ }
196
+
197
+ // Classificação por estrelas
198
+ if (settings.showRating) {
199
+ const starSize = 25;
200
+ const starSpacing = 5;
201
+ const startX = textX;
202
+ const startY = settings.height * 0.6;
203
+
204
+ for (let i = 0; i < 5; i++) {
205
+ const starX = startX + i * (starSize + starSpacing);
206
+ const filled = i < settings.rating;
207
+
208
+ // Desenha estrela
209
+ ctx.fillStyle = filled ? '#FFD700' : '#DDDDDD';
210
+ utils.drawStar(ctx, starX, startY, starSize);
211
+ }
212
+ }
213
+
214
+ // Tags
215
+ if (settings.tags.length > 0) {
216
+ const tagHeight = 30;
217
+ const tagSpacing = 10;
218
+ const tagY = settings.height * 0.65;
219
+ let currentX = textX;
220
+
221
+ ctx.font = `16px ${settings.font}`;
222
+
223
+ for (const tag of settings.tags) {
224
+ const tagWidth = ctx.measureText(tag).width + 20;
225
+
226
+ // Desenha o fundo da tag
227
+ ctx.fillStyle = utils.adjustColor(settings.accentColor, 0.2);
228
+ utils.roundRect(ctx, currentX, tagY, tagWidth, tagHeight, 15, true);
229
+
230
+ // Desenha o texto da tag
231
+ ctx.fillStyle = settings.textColor;
232
+ ctx.textAlign = 'center';
233
+ ctx.fillText(tag, currentX + tagWidth / 2, tagY + 20);
234
+ ctx.textAlign = 'left';
235
+
236
+ currentX += tagWidth + tagSpacing;
237
+
238
+ // Quebra para a próxima linha se necessário
239
+ if (currentX + tagWidth > textX + textWidth) {
240
+ break;
241
+ }
242
+ }
243
+ }
244
+
245
+ // Botão CTA
246
+ const ctaWidth = 250;
247
+ const ctaHeight = 60;
248
+ const ctaX = textX;
249
+ const ctaY = settings.height * 0.75;
250
+
251
+ // Desenha o fundo do botão
252
+ ctx.fillStyle = settings.accentColor;
253
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true);
254
+
255
+ // Desenha o texto do botão
256
+ ctx.font = `bold 24px ${settings.font}`;
257
+ ctx.fillStyle = '#FFFFFF';
258
+ ctx.textAlign = 'center';
259
+ ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
260
+ ctx.textAlign = 'left';
261
+
262
+ // Desenha borda se necessário
263
+ if (settings.showBorder) {
264
+ ctx.strokeStyle = settings.borderColor;
265
+ ctx.lineWidth = settings.borderWidth;
266
+ utils.roundRect(ctx, settings.borderWidth / 2, settings.borderWidth / 2,
267
+ settings.width - settings.borderWidth, settings.height - settings.borderWidth,
268
+ settings.borderRadius, false, true);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Desenha o estilo minimalista do banner de produto
274
+ *
275
+ * @async
276
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
277
+ * @param {Object} settings - Configurações do banner
278
+ */
279
+ async function drawMinimalStyle(ctx, settings) {
280
+ // Desenha o fundo
281
+ ctx.fillStyle = settings.backgroundColor;
282
+ ctx.fillRect(0, 0, settings.width, settings.height);
283
+
284
+ // Carrega a imagem do produto
285
+ let productImage = await utils.loadImage(settings.productImageURL);
286
+
287
+ // Aplica filtro se necessário
288
+ if (settings.applyFilter && settings.filterType !== 'none') {
289
+ productImage = await imageFilters.applyFilter(
290
+ productImage,
291
+ settings.filterType
292
+ );
293
+ }
294
+
295
+ // Calcula as dimensões e posições
296
+ const productWidth = settings.width * 0.35;
297
+ const productHeight = settings.height * 0.65;
298
+ const productX = settings.width * 0.55;
299
+ const productY = (settings.height - productHeight) / 2;
300
+
301
+ // Desenha a imagem do produto
302
+ utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
303
+
304
+ // Desenha o conteúdo de texto
305
+ const textX = settings.width * 0.1;
306
+ const textWidth = productX - textX - 50;
307
+
308
+ // Nome do produto
309
+ ctx.font = `300 42px ${settings.font}`;
310
+ ctx.fillStyle = settings.textColor;
311
+ utils.wrapText(ctx, settings.productName, textX, settings.height * 0.35, textWidth, 50);
312
+
313
+ // Preço
314
+ if (settings.price) {
315
+ ctx.font = `bold 54px ${settings.font}`;
316
+ ctx.fillStyle = settings.textColor;
317
+ ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.55);
318
+ }
319
+
320
+ // Badge de desconto
321
+ if (settings.discountBadge) {
322
+ ctx.font = `bold 24px ${settings.font}`;
323
+ ctx.fillStyle = settings.accentColor;
324
+ ctx.fillText(settings.discountBadge, textX, settings.height * 0.65);
325
+ }
326
+
327
+ // Botão CTA
328
+ const ctaWidth = 200;
329
+ const ctaHeight = 50;
330
+ const ctaX = textX;
331
+ const ctaY = settings.height * 0.75;
332
+
333
+ // Desenha a borda do botão
334
+ ctx.strokeStyle = settings.accentColor;
335
+ ctx.lineWidth = 2;
336
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
337
+
338
+ // Desenha o texto do botão
339
+ ctx.font = `bold 20px ${settings.font}`;
340
+ ctx.fillStyle = settings.accentColor;
341
+ ctx.textAlign = 'center';
342
+ ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33);
343
+ ctx.textAlign = 'left';
344
+ }
345
+
346
+ /**
347
+ * Desenha o estilo bold do banner de produto
348
+ *
349
+ * @async
350
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
351
+ * @param {Object} settings - Configurações do banner
352
+ */
353
+ async function drawBoldStyle(ctx, settings) {
354
+ // Desenha o fundo
355
+ ctx.fillStyle = settings.accentColor;
356
+ ctx.fillRect(0, 0, settings.width, settings.height);
357
+
358
+ // Carrega a imagem do produto
359
+ let productImage = await utils.loadImage(settings.productImageURL);
360
+
361
+ // Aplica filtro se necessário
362
+ if (settings.applyFilter && settings.filterType !== 'none') {
363
+ productImage = await imageFilters.applyFilter(
364
+ productImage,
365
+ settings.filterType
366
+ );
367
+ }
368
+
369
+ // Calcula as dimensões e posições
370
+ const productWidth = settings.width * 0.45;
371
+ const productHeight = settings.height * 0.8;
372
+ const productX = settings.width * 0.5;
373
+ const productY = (settings.height - productHeight) / 2;
374
+
375
+ // Desenha a imagem do produto
376
+ utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
377
+
378
+ // Desenha o conteúdo de texto
379
+ const textX = settings.width * 0.08;
380
+ const textWidth = productX - textX - 30;
381
+
382
+ // Nome do produto
383
+ ctx.font = `bold 52px ${settings.font}`;
384
+ ctx.fillStyle = '#FFFFFF';
385
+ utils.wrapText(ctx, settings.productName.toUpperCase(), textX, settings.height * 0.3, textWidth, 60);
386
+
387
+ // Preço
388
+ if (settings.price) {
389
+ ctx.font = `bold 72px ${settings.font}`;
390
+ ctx.fillStyle = '#FFFFFF';
391
+ ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
392
+ }
393
+
394
+ // Badge de desconto
395
+ if (settings.discountBadge) {
396
+ const badgeWidth = 140;
397
+ const badgeHeight = 50;
398
+ const badgeX = textX;
399
+ const badgeY = settings.height * 0.55;
400
+
401
+ // Desenha o fundo do badge
402
+ ctx.fillStyle = '#FFFFFF';
403
+ utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true);
404
+
405
+ // Desenha o texto do badge
406
+ ctx.font = `bold 24px ${settings.font}`;
407
+ ctx.fillStyle = settings.accentColor;
408
+ ctx.textAlign = 'center';
409
+ ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35);
410
+ ctx.textAlign = 'left';
411
+ }
412
+
413
+ // Botão CTA
414
+ const ctaWidth = 250;
415
+ const ctaHeight = 70;
416
+ const ctaX = textX;
417
+ const ctaY = settings.height * 0.7;
418
+
419
+ // Desenha o fundo do botão
420
+ ctx.fillStyle = '#FFFFFF';
421
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
422
+
423
+ // Desenha o texto do botão
424
+ ctx.font = `bold 26px ${settings.font}`;
425
+ ctx.fillStyle = settings.accentColor;
426
+ ctx.textAlign = 'center';
427
+ ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43);
428
+ ctx.textAlign = 'left';
429
+ }
430
+
431
+ /**
432
+ * Desenha o estilo gradient do banner de produto
433
+ *
434
+ * @async
435
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
436
+ * @param {Object} settings - Configurações do banner
437
+ */
438
+ async function drawGradientStyle(ctx, settings) {
439
+ // Cria um gradiente de fundo
440
+ const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
441
+ gradient.addColorStop(0, settings.accentColor);
442
+ gradient.addColorStop(1, utils.adjustColor(settings.accentColor, -0.3));
443
+
444
+ // Desenha o fundo
445
+ ctx.fillStyle = gradient;
446
+ ctx.fillRect(0, 0, settings.width, settings.height);
447
+
448
+ // Adiciona um padrão de pontos ao fundo
449
+ effects.addDotPattern(ctx, settings.width, settings.height, 20, 'rgba(255,255,255,0.1)');
450
+
451
+ // Carrega a imagem do produto
452
+ let productImage = await utils.loadImage(settings.productImageURL);
453
+
454
+ // Aplica filtro se necessário
455
+ if (settings.applyFilter && settings.filterType !== 'none') {
456
+ productImage = await imageFilters.applyFilter(
457
+ productImage,
458
+ settings.filterType
459
+ );
460
+ }
461
+
462
+ // Calcula as dimensões e posições
463
+ const productWidth = settings.width * 0.4;
464
+ const productHeight = settings.height * 0.75;
465
+ const productX = settings.width * 0.05;
466
+ const productY = (settings.height - productHeight) / 2;
467
+
468
+ // Adiciona sombra à imagem do produto
469
+ if (settings.showShadow) {
470
+ ctx.shadowColor = settings.shadowColor;
471
+ ctx.shadowBlur = 30;
472
+ ctx.shadowOffsetX = 10;
473
+ ctx.shadowOffsetY = 10;
474
+ }
475
+
476
+ // Desenha a imagem do produto
477
+ utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
478
+
479
+ // Remove a sombra
480
+ ctx.shadowColor = 'transparent';
481
+ ctx.shadowBlur = 0;
482
+ ctx.shadowOffsetX = 0;
483
+ ctx.shadowOffsetY = 0;
484
+
485
+ // Desenha o conteúdo de texto
486
+ const textX = productX + productWidth + 60;
487
+ const textWidth = settings.width - textX - 60;
488
+
489
+ // Adiciona um retângulo semi-transparente para o texto
490
+ ctx.fillStyle = 'rgba(255,255,255,0.15)';
491
+ utils.roundRect(ctx, textX - 20, settings.height * 0.2, textWidth + 40, settings.height * 0.6, 20, true);
492
+
493
+ // Nome do produto
494
+ ctx.font = `bold 48px ${settings.font}`;
495
+ ctx.fillStyle = '#FFFFFF';
496
+ utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60);
497
+
498
+ // Preço
499
+ if (settings.price) {
500
+ ctx.font = `bold 64px ${settings.font}`;
501
+ ctx.fillStyle = '#FFFFFF';
502
+
503
+ // Adiciona sombra ao texto do preço
504
+ ctx.shadowColor = 'rgba(0,0,0,0.3)';
505
+ ctx.shadowBlur = 5;
506
+ ctx.shadowOffsetX = 2;
507
+ ctx.shadowOffsetY = 2;
508
+
509
+ ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
510
+
511
+ // Remove a sombra
512
+ ctx.shadowColor = 'transparent';
513
+ ctx.shadowBlur = 0;
514
+ ctx.shadowOffsetX = 0;
515
+ ctx.shadowOffsetY = 0;
516
+ }
517
+
518
+ // Badge de desconto
519
+ if (settings.discountBadge) {
520
+ const badgeWidth = 140;
521
+ const badgeHeight = 50;
522
+ const badgeX = textX;
523
+ const badgeY = settings.height * 0.55;
524
+
525
+ // Desenha o fundo do badge
526
+ ctx.fillStyle = '#FFFFFF';
527
+ utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true);
528
+
529
+ // Desenha o texto do badge
530
+ ctx.font = `bold 24px ${settings.font}`;
531
+ ctx.fillStyle = settings.accentColor;
532
+ ctx.textAlign = 'center';
533
+ ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35);
534
+ ctx.textAlign = 'left';
535
+ }
536
+
537
+ // Botão CTA
538
+ const ctaWidth = 250;
539
+ const ctaHeight = 70;
540
+ const ctaX = textX;
541
+ const ctaY = settings.height * 0.7;
542
+
543
+ // Desenha o fundo do botão com gradiente
544
+ const ctaGradient = ctx.createLinearGradient(ctaX, ctaY, ctaX + ctaWidth, ctaY + ctaHeight);
545
+ ctaGradient.addColorStop(0, '#FFFFFF');
546
+ ctaGradient.addColorStop(1, '#F0F0F0');
547
+
548
+ ctx.fillStyle = ctaGradient;
549
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
550
+
551
+ // Desenha o texto do botão
552
+ ctx.font = `bold 26px ${settings.font}`;
553
+ ctx.fillStyle = settings.accentColor;
554
+ ctx.textAlign = 'center';
555
+ ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43);
556
+ ctx.textAlign = 'left';
557
+ }
558
+
559
+ /**
560
+ * Cria um banner de promoção para e-commerce
561
+ *
562
+ * @async
563
+ * @param {Object} options - Opções de configuração
564
+ * @param {string} options.title - Título da promoção
565
+ * @param {string} [options.subtitle] - Subtítulo da promoção
566
+ * @param {string} [options.backgroundImageURL] - URL da imagem de fundo
567
+ * @param {string} [options.discountText] - Texto de desconto (ex: "50% OFF")
568
+ * @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action
569
+ * @param {string} [options.validUntil] - Data de validade da promoção
570
+ * @param {string} [options.backgroundColor="#FF6B6B"] - Cor de fundo
571
+ * @param {string} [options.textColor="#FFFFFF"] - Cor do texto
572
+ * @param {string} [options.accentColor="#FFFFFF"] - Cor de destaque
573
+ * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "seasonal")
574
+ * @param {number} [options.width=1200] - Largura do banner em pixels
575
+ * @param {number} [options.height=628] - Altura do banner em pixels
576
+ * @param {string} [options.font="Poppins"] - Nome da fonte
577
+ * @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem de fundo
578
+ * @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição
579
+ * @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
580
+ * @param {string} [options.season] - Temporada para o estilo seasonal ("christmas", "halloween", "summer", "blackfriday")
581
+ * @returns {Promise<Buffer>} - Buffer da imagem gerada
582
+ */
583
+ async function createPromotionBanner(options) {
584
+ // Validação de parâmetros
585
+ validator.validateRequiredParams(options, ['title']);
586
+
587
+ // Valores padrão
588
+ const defaults = {
589
+ subtitle: '',
590
+ backgroundImageURL: '',
591
+ discountText: '',
592
+ ctaText: 'COMPRAR AGORA',
593
+ validUntil: '',
594
+ backgroundColor: '#FF6B6B',
595
+ textColor: '#FFFFFF',
596
+ accentColor: '#FFFFFF',
597
+ style: 'standard',
598
+ width: 1200,
599
+ height: 628,
600
+ font: 'Poppins',
601
+ showOverlay: true,
602
+ overlayOpacity: 0.5,
603
+ overlayColor: '#000000',
604
+ season: ''
605
+ };
606
+
607
+ // Mescla as opções com os valores padrão
608
+ const settings = { ...defaults, ...options };
609
+
610
+ // Cria a imagem
611
+ const img = PImage.make(settings.width, settings.height);
612
+ const ctx = img.getContext('2d');
613
+
614
+ try {
615
+ // Aplica o estilo de acordo com a opção selecionada
616
+ switch (settings.style) {
617
+ case 'minimal':
618
+ await drawMinimalPromotionStyle(ctx, settings);
619
+ break;
620
+ case 'bold':
621
+ await drawBoldPromotionStyle(ctx, settings);
622
+ break;
623
+ case 'seasonal':
624
+ await drawSeasonalPromotionStyle(ctx, settings);
625
+ break;
626
+ case 'standard':
627
+ default:
628
+ await drawStandardPromotionStyle(ctx, settings);
629
+ break;
630
+ }
631
+
632
+ // Retorna o buffer da imagem
633
+ return await utils.getBufferFromImage(img);
634
+ } catch (error) {
635
+ console.error('Erro ao criar banner de promoção:', error);
636
+ throw error;
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Desenha o estilo padrão do banner de promoção
642
+ *
643
+ * @async
644
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
645
+ * @param {Object} settings - Configurações do banner
646
+ */
647
+ async function drawStandardPromotionStyle(ctx, settings) {
648
+ // Desenha o fundo
649
+ ctx.fillStyle = settings.backgroundColor;
650
+ ctx.fillRect(0, 0, settings.width, settings.height);
651
+
652
+ // Se tiver imagem de fundo, desenha com sobreposição
653
+ if (settings.backgroundImageURL) {
654
+ const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
655
+ ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
656
+
657
+ // Adiciona sobreposição se necessário
658
+ if (settings.showOverlay) {
659
+ ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity);
660
+ ctx.fillRect(0, 0, settings.width, settings.height);
661
+ }
662
+ }
663
+
664
+ // Desenha o conteúdo de texto centralizado
665
+ ctx.textAlign = 'center';
666
+
667
+ // Título
668
+ ctx.font = `bold 72px ${settings.font}`;
669
+ ctx.fillStyle = settings.textColor;
670
+ utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80);
671
+
672
+ // Subtítulo
673
+ if (settings.subtitle) {
674
+ ctx.font = `300 36px ${settings.font}`;
675
+ ctx.fillStyle = settings.textColor;
676
+ utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50);
677
+ }
678
+
679
+ // Texto de desconto
680
+ if (settings.discountText) {
681
+ const discountSize = 120;
682
+ const discountX = settings.width / 2;
683
+ const discountY = settings.height * 0.65;
684
+
685
+ // Desenha um círculo para o desconto
686
+ ctx.fillStyle = settings.accentColor;
687
+ ctx.beginPath();
688
+ ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2);
689
+ ctx.fill();
690
+
691
+ // Desenha o texto de desconto
692
+ ctx.font = `bold 48px ${settings.font}`;
693
+ ctx.fillStyle = settings.backgroundColor;
694
+ ctx.fillText(settings.discountText, discountX, discountY + 15);
695
+ }
696
+
697
+ // Botão CTA
698
+ const ctaWidth = 300;
699
+ const ctaHeight = 70;
700
+ const ctaX = (settings.width - ctaWidth) / 2;
701
+ const ctaY = settings.height * 0.8;
702
+
703
+ // Desenha o fundo do botão
704
+ ctx.fillStyle = settings.accentColor;
705
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
706
+
707
+ // Desenha o texto do botão
708
+ ctx.font = `bold 28px ${settings.font}`;
709
+ ctx.fillStyle = settings.backgroundColor;
710
+ ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
711
+
712
+ // Data de validade
713
+ if (settings.validUntil) {
714
+ ctx.font = `16px ${settings.font}`;
715
+ ctx.fillStyle = settings.textColor;
716
+ ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
717
+ }
718
+
719
+ // Restaura o alinhamento de texto
720
+ ctx.textAlign = 'left';
721
+ }
722
+
723
+ /**
724
+ * Desenha o estilo minimalista do banner de promoção
725
+ *
726
+ * @async
727
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
728
+ * @param {Object} settings - Configurações do banner
729
+ */
730
+ async function drawMinimalPromotionStyle(ctx, settings) {
731
+ // Desenha o fundo
732
+ ctx.fillStyle = '#FFFFFF';
733
+ ctx.fillRect(0, 0, settings.width, settings.height);
734
+
735
+ // Adiciona uma borda fina
736
+ ctx.strokeStyle = settings.backgroundColor;
737
+ ctx.lineWidth = 3;
738
+ ctx.strokeRect(20, 20, settings.width - 40, settings.height - 40);
739
+
740
+ // Desenha o conteúdo de texto centralizado
741
+ ctx.textAlign = 'center';
742
+
743
+ // Título
744
+ ctx.font = `bold 64px ${settings.font}`;
745
+ ctx.fillStyle = settings.backgroundColor;
746
+ utils.wrapTextCentered(ctx, settings.title, settings.width / 2, settings.height * 0.35, settings.width * 0.7, 70);
747
+
748
+ // Subtítulo
749
+ if (settings.subtitle) {
750
+ ctx.font = `300 32px ${settings.font}`;
751
+ ctx.fillStyle = '#333333';
752
+ utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.6, 40);
753
+ }
754
+
755
+ // Texto de desconto
756
+ if (settings.discountText) {
757
+ ctx.font = `bold 96px ${settings.font}`;
758
+ ctx.fillStyle = settings.backgroundColor;
759
+ ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.7);
760
+ }
761
+
762
+ // Botão CTA
763
+ const ctaWidth = 250;
764
+ const ctaHeight = 60;
765
+ const ctaX = (settings.width - ctaWidth) / 2;
766
+ const ctaY = settings.height * 0.8;
767
+
768
+ // Desenha a borda do botão
769
+ ctx.strokeStyle = settings.backgroundColor;
770
+ ctx.lineWidth = 2;
771
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
772
+
773
+ // Desenha o texto do botão
774
+ ctx.font = `bold 24px ${settings.font}`;
775
+ ctx.fillStyle = settings.backgroundColor;
776
+ ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 38);
777
+
778
+ // Data de validade
779
+ if (settings.validUntil) {
780
+ ctx.font = `16px ${settings.font}`;
781
+ ctx.fillStyle = '#333333';
782
+ ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
783
+ }
784
+
785
+ // Restaura o alinhamento de texto
786
+ ctx.textAlign = 'left';
787
+ }
788
+
789
+ /**
790
+ * Desenha o estilo bold do banner de promoção
791
+ *
792
+ * @async
793
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
794
+ * @param {Object} settings - Configurações do banner
795
+ */
796
+ async function drawBoldPromotionStyle(ctx, settings) {
797
+ // Cria um gradiente de fundo
798
+ const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
799
+ gradient.addColorStop(0, settings.backgroundColor);
800
+ gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.3));
801
+
802
+ // Desenha o fundo
803
+ ctx.fillStyle = gradient;
804
+ ctx.fillRect(0, 0, settings.width, settings.height);
805
+
806
+ // Adiciona elementos decorativos
807
+ for (let i = 0; i < 5; i++) {
808
+ const size = Math.random() * 200 + 100;
809
+ const x = Math.random() * settings.width;
810
+ const y = Math.random() * settings.height;
811
+
812
+ ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.1);
813
+ ctx.beginPath();
814
+ ctx.arc(x, y, size, 0, Math.PI * 2);
815
+ ctx.fill();
816
+ }
817
+
818
+ // Desenha o conteúdo de texto centralizado
819
+ ctx.textAlign = 'center';
820
+
821
+ // Texto de desconto
822
+ if (settings.discountText) {
823
+ ctx.font = `bold 120px ${settings.font}`;
824
+ ctx.fillStyle = settings.accentColor;
825
+
826
+ // Adiciona sombra ao texto
827
+ ctx.shadowColor = 'rgba(0,0,0,0.3)';
828
+ ctx.shadowBlur = 10;
829
+ ctx.shadowOffsetX = 5;
830
+ ctx.shadowOffsetY = 5;
831
+
832
+ ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.4);
833
+
834
+ // Remove a sombra
835
+ ctx.shadowColor = 'transparent';
836
+ ctx.shadowBlur = 0;
837
+ ctx.shadowOffsetX = 0;
838
+ ctx.shadowOffsetY = 0;
839
+ }
840
+
841
+ // Título
842
+ ctx.font = `bold 72px ${settings.font}`;
843
+ ctx.fillStyle = settings.textColor;
844
+ utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.55, settings.width * 0.8, 80);
845
+
846
+ // Subtítulo
847
+ if (settings.subtitle) {
848
+ ctx.font = `300 36px ${settings.font}`;
849
+ ctx.fillStyle = settings.textColor;
850
+ utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.7, settings.width * 0.7, 50);
851
+ }
852
+
853
+ // Botão CTA
854
+ const ctaWidth = 350;
855
+ const ctaHeight = 80;
856
+ const ctaX = (settings.width - ctaWidth) / 2;
857
+ const ctaY = settings.height * 0.8;
858
+
859
+ // Desenha o fundo do botão
860
+ ctx.fillStyle = settings.accentColor;
861
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 40, true);
862
+
863
+ // Desenha o texto do botão
864
+ ctx.font = `bold 32px ${settings.font}`;
865
+ ctx.fillStyle = settings.backgroundColor;
866
+ ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 50);
867
+
868
+ // Data de validade
869
+ if (settings.validUntil) {
870
+ ctx.font = `18px ${settings.font}`;
871
+ ctx.fillStyle = settings.textColor;
872
+ ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
873
+ }
874
+
875
+ // Restaura o alinhamento de texto
876
+ ctx.textAlign = 'left';
877
+ }
878
+
879
+ /**
880
+ * Desenha o estilo seasonal do banner de promoção
881
+ *
882
+ * @async
883
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
884
+ * @param {Object} settings - Configurações do banner
885
+ */
886
+ async function drawSeasonalPromotionStyle(ctx, settings) {
887
+ // Configurações específicas para cada temporada
888
+ const seasonalSettings = {
889
+ christmas: {
890
+ backgroundColor: '#D42426',
891
+ accentColor: '#0F8A5F',
892
+ textColor: '#FFFFFF',
893
+ decorations: ['snowflake', 'gift', 'tree']
894
+ },
895
+ halloween: {
896
+ backgroundColor: '#FF6600',
897
+ accentColor: '#000000',
898
+ textColor: '#FFFFFF',
899
+ decorations: ['pumpkin', 'ghost', 'bat']
900
+ },
901
+ summer: {
902
+ backgroundColor: '#00BFFF',
903
+ accentColor: '#FFD700',
904
+ textColor: '#FFFFFF',
905
+ decorations: ['sun', 'palm', 'wave']
906
+ },
907
+ blackfriday: {
908
+ backgroundColor: '#000000',
909
+ accentColor: '#FF0000',
910
+ textColor: '#FFFFFF',
911
+ decorations: ['tag', 'cart', 'discount']
912
+ }
913
+ };
914
+
915
+ // Usa as configurações da temporada selecionada ou as configurações padrão
916
+ const season = settings.season.toLowerCase();
917
+ const currentSeason = seasonalSettings[season] || {
918
+ backgroundColor: settings.backgroundColor,
919
+ accentColor: settings.accentColor,
920
+ textColor: settings.textColor,
921
+ decorations: []
922
+ };
923
+
924
+ // Desenha o fundo
925
+ ctx.fillStyle = currentSeason.backgroundColor;
926
+ ctx.fillRect(0, 0, settings.width, settings.height);
927
+
928
+ // Se tiver imagem de fundo, desenha com sobreposição
929
+ if (settings.backgroundImageURL) {
930
+ const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
931
+ ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
932
+
933
+ // Adiciona sobreposição se necessário
934
+ if (settings.showOverlay) {
935
+ ctx.fillStyle = utils.hexToRgba(currentSeason.backgroundColor, settings.overlayOpacity);
936
+ ctx.fillRect(0, 0, settings.width, settings.height);
937
+ }
938
+ }
939
+
940
+ // Adiciona decorações sazonais
941
+ if (currentSeason.decorations.length > 0) {
942
+ await addSeasonalDecorations(ctx, settings, currentSeason.decorations);
943
+ }
944
+
945
+ // Desenha o conteúdo de texto centralizado
946
+ ctx.textAlign = 'center';
947
+
948
+ // Título
949
+ ctx.font = `bold 72px ${settings.font}`;
950
+ ctx.fillStyle = currentSeason.textColor;
951
+ utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80);
952
+
953
+ // Subtítulo
954
+ if (settings.subtitle) {
955
+ ctx.font = `300 36px ${settings.font}`;
956
+ ctx.fillStyle = currentSeason.textColor;
957
+ utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50);
958
+ }
959
+
960
+ // Texto de desconto
961
+ if (settings.discountText) {
962
+ const discountSize = 130;
963
+ const discountX = settings.width / 2;
964
+ const discountY = settings.height * 0.65;
965
+
966
+ // Desenha um círculo para o desconto
967
+ ctx.fillStyle = currentSeason.accentColor;
968
+ ctx.beginPath();
969
+ ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2);
970
+ ctx.fill();
971
+
972
+ // Desenha o texto de desconto
973
+ ctx.font = `bold 54px ${settings.font}`;
974
+ ctx.fillStyle = currentSeason.textColor;
975
+ ctx.fillText(settings.discountText, discountX, discountY + 15);
976
+ }
977
+
978
+ // Botão CTA
979
+ const ctaWidth = 300;
980
+ const ctaHeight = 70;
981
+ const ctaX = (settings.width - ctaWidth) / 2;
982
+ const ctaY = settings.height * 0.8;
983
+
984
+ // Desenha o fundo do botão
985
+ ctx.fillStyle = currentSeason.accentColor;
986
+ utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
987
+
988
+ // Desenha o texto do botão
989
+ ctx.font = `bold 28px ${settings.font}`;
990
+ ctx.fillStyle = currentSeason.textColor;
991
+ ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
992
+
993
+ // Data de validade
994
+ if (settings.validUntil) {
995
+ ctx.font = `16px ${settings.font}`;
996
+ ctx.fillStyle = currentSeason.textColor;
997
+ ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
998
+ }
999
+
1000
+ // Restaura o alinhamento de texto
1001
+ ctx.textAlign = 'left';
1002
+ }
1003
+
1004
+ /**
1005
+ * Adiciona decorações sazonais ao banner
1006
+ *
1007
+ * @async
1008
+ * @param {CanvasRenderingContext2D} ctx - Contexto de renderização
1009
+ * @param {Object} settings - Configurações do banner
1010
+ * @param {Array<string>} decorations - Lista de decorações a serem adicionadas
1011
+ */
1012
+ async function addSeasonalDecorations(ctx, settings, decorations) {
1013
+ // Implementação simplificada - em uma versão real, carregaria imagens de decorações
1014
+ // ou desenharia formas específicas para cada tipo de decoração
1015
+
1016
+ for (let i = 0; i < 10; i++) {
1017
+ const size = Math.random() * 50 + 20;
1018
+ const x = Math.random() * settings.width;
1019
+ const y = Math.random() * settings.height;
1020
+
1021
+ ctx.fillStyle = utils.hexToRgba('#FFFFFF', 0.2);
1022
+
1023
+ // Escolhe uma decoração aleatória da lista
1024
+ const decoration = decorations[Math.floor(Math.random() * decorations.length)];
1025
+
1026
+ switch (decoration) {
1027
+ case 'snowflake':
1028
+ drawSnowflake(ctx, x, y, size);
1029
+ break;
1030
+ case 'gift':
1031
+ drawGift(ctx, x, y, size);
1032
+ break;
1033
+ case 'tree':
1034
+ drawTree(ctx, x, y, size);
1035
+ break;
1036
+ case 'pumpkin':
1037
+ drawPumpkin(ctx, x, y, size);
1038
+ break;
1039
+ case 'ghost':
1040
+ drawGhost(ctx, x, y, size);
1041
+ break;
1042
+ case 'bat':
1043
+ drawBat(ctx, x, y, size);
1044
+ break;
1045
+ case 'sun':
1046
+ drawSun(ctx, x, y, size);
1047
+ break;
1048
+ case 'palm':
1049
+ drawPalm(ctx, x, y, size);
1050
+ break;
1051
+ case 'wave':
1052
+ drawWave(ctx, x, y, size);
1053
+ break;
1054
+ case 'tag':
1055
+ drawTag(ctx, x, y, size);
1056
+ break;
1057
+ case 'cart':
1058
+ drawCart(ctx, x, y, size);
1059
+ break;
1060
+ case 'discount':
1061
+ drawDiscount(ctx, x, y, size);
1062
+ break;
1063
+ default:
1064
+ // Desenha um círculo como fallback
1065
+ ctx.beginPath();
1066
+ ctx.arc(x, y, size / 2, 0, Math.PI * 2);
1067
+ ctx.fill();
1068
+ break;
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ // Funções para desenhar decorações sazonais
1074
+ // Estas são implementações simplificadas - em uma versão real, seriam mais detalhadas
1075
+
1076
+ function drawSnowflake(ctx, x, y, size) {
1077
+ ctx.beginPath();
1078
+ for (let i = 0; i < 6; i++) {
1079
+ const angle = (Math.PI / 3) * i;
1080
+ ctx.moveTo(x, y);
1081
+ ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);
1082
+ }
1083
+ ctx.stroke();
1084
+ }
1085
+
1086
+ function drawGift(ctx, x, y, size) {
1087
+ ctx.fillRect(x - size / 2, y - size / 2, size, size);
1088
+ ctx.fillRect(x - size / 4, y - size / 2 - size / 4, size / 2, size * 1.5);
1089
+ ctx.fillRect(x - size / 2 - size / 4, y - size / 4, size * 1.5, size / 2);
1090
+ }
1091
+
1092
+ function drawTree(ctx, x, y, size) {
1093
+ ctx.beginPath();
1094
+ ctx.moveTo(x, y - size);
1095
+ ctx.lineTo(x + size / 2, y);
1096
+ ctx.lineTo(x - size / 2, y);
1097
+ ctx.closePath();
1098
+ ctx.fill();
1099
+ ctx.fillRect(x - size / 6, y, size / 3, size / 2);
1100
+ }
1101
+
1102
+ function drawPumpkin(ctx, x, y, size) {
1103
+ ctx.beginPath();
1104
+ ctx.arc(x, y, size / 2, 0, Math.PI * 2);
1105
+ ctx.fill();
1106
+ ctx.fillRect(x - size / 10, y - size / 2 - size / 5, size / 5, size / 5);
1107
+ }
1108
+
1109
+ function drawGhost(ctx, x, y, size) {
1110
+ ctx.beginPath();
1111
+ ctx.arc(x, y - size / 3, size / 2, Math.PI, 0, true);
1112
+ ctx.lineTo(x + size / 2, y + size / 2);
1113
+ ctx.lineTo(x + size / 4, y + size / 4);
1114
+ ctx.lineTo(x, y + size / 2);
1115
+ ctx.lineTo(x - size / 4, y + size / 4);
1116
+ ctx.lineTo(x - size / 2, y + size / 2);
1117
+ ctx.closePath();
1118
+ ctx.fill();
1119
+ }
1120
+
1121
+ function drawBat(ctx, x, y, size) {
1122
+ ctx.beginPath();
1123
+ ctx.arc(x, y, size / 4, 0, Math.PI * 2);
1124
+ ctx.fill();
1125
+ ctx.beginPath();
1126
+ ctx.arc(x - size / 2, y - size / 4, size / 3, 0, Math.PI * 2);
1127
+ ctx.fill();
1128
+ ctx.beginPath();
1129
+ ctx.arc(x + size / 2, y - size / 4, size / 3, 0, Math.PI * 2);
1130
+ ctx.fill();
1131
+ }
1132
+
1133
+ function drawSun(ctx, x, y, size) {
1134
+ ctx.beginPath();
1135
+ ctx.arc(x, y, size / 2, 0, Math.PI * 2);
1136
+ ctx.fill();
1137
+ for (let i = 0; i < 8; i++) {
1138
+ const angle = (Math.PI / 4) * i;
1139
+ ctx.beginPath();
1140
+ ctx.moveTo(x + Math.cos(angle) * size / 2, y + Math.sin(angle) * size / 2);
1141
+ ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);
1142
+ ctx.stroke();
1143
+ }
1144
+ }
1145
+
1146
+ function drawPalm(ctx, x, y, size) {
1147
+ ctx.fillRect(x - size / 10, y, size / 5, size);
1148
+ for (let i = 0; i < 5; i++) {
1149
+ const angle = (Math.PI / 6) + (Math.PI / 12) * i;
1150
+ ctx.beginPath();
1151
+ ctx.ellipse(x, y, size / 3, size, angle, 0, Math.PI);
1152
+ ctx.fill();
1153
+ }
1154
+ }
1155
+
1156
+ function drawWave(ctx, x, y, size) {
1157
+ ctx.beginPath();
1158
+ ctx.moveTo(x - size, y);
1159
+ for (let i = 0; i < 4; i++) {
1160
+ const cp1x = x - size + (i * size / 2);
1161
+ const cp1y = y + ((i % 2 === 0) ? -size / 3 : size / 3);
1162
+ const cp2x = x - size + (i * size / 2) + size / 4;
1163
+ const cp2y = y + ((i % 2 === 0) ? size / 3 : -size / 3);
1164
+ const endX = x - size + ((i + 1) * size / 2);
1165
+ const endY = y;
1166
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
1167
+ }
1168
+ ctx.stroke();
1169
+ }
1170
+
1171
+ function drawTag(ctx, x, y, size) {
1172
+ ctx.beginPath();
1173
+ ctx.moveTo(x, y - size / 2);
1174
+ ctx.lineTo(x + size / 2, y);
1175
+ ctx.lineTo(x, y + size / 2);
1176
+ ctx.lineTo(x - size / 2, y);
1177
+ ctx.closePath();
1178
+ ctx.fill();
1179
+ ctx.beginPath();
1180
+ ctx.arc(x, y, size / 6, 0, Math.PI * 2);
1181
+ ctx.stroke();
1182
+ }
1183
+
1184
+ function drawCart(ctx, x, y, size) {
1185
+ ctx.beginPath();
1186
+ ctx.moveTo(x - size / 2, y);
1187
+ ctx.lineTo(x + size / 2, y);
1188
+ ctx.lineTo(x + size / 3, y - size / 2);
1189
+ ctx.lineTo(x - size / 3, y - size / 2);
1190
+ ctx.closePath();
1191
+ ctx.fill();
1192
+ ctx.beginPath();
1193
+ ctx.arc(x - size / 4, y + size / 6, size / 6, 0, Math.PI * 2);
1194
+ ctx.fill();
1195
+ ctx.beginPath();
1196
+ ctx.arc(x + size / 4, y + size / 6, size / 6, 0, Math.PI * 2);
1197
+ ctx.fill();
1198
+ }
1199
+
1200
+ function drawDiscount(ctx, x, y, size) {
1201
+ ctx.beginPath();
1202
+ ctx.arc(x, y, size / 2, 0, Math.PI * 2);
1203
+ ctx.stroke();
1204
+ ctx.beginPath();
1205
+ ctx.moveTo(x - size / 3, y - size / 3);
1206
+ ctx.lineTo(x + size / 3, y + size / 3);
1207
+ ctx.stroke();
1208
+ }
1209
+
1210
+ module.exports = {
1211
+ createProductBanner,
1212
+ createPromotionBanner
1213
+ };
1214
+