@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,679 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Módulo de Banner de Post do Facebook
5
+ *
6
+ * Este módulo gera banners no estilo de posts do Facebook com
7
+ * elementos visuais característicos da plataforma.
8
+ *
9
+ * @author Cognima Team (melhorado)
10
+ * @version 2.0.0
11
+ */
12
+
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+
15
+ const pureimage = require("pureimage");
16
+ const path = require("path");
17
+ const {
18
+ loadImageWithAxios,
19
+ encodeToBuffer,
20
+ roundRect,
21
+ wrapText,
22
+ registerFontIfNeeded,
23
+ isValidHexColor,
24
+ DEFAULT_FONT_FAMILY,
25
+ applyTextShadow,
26
+ clearShadow,
27
+ createLinearGradient,
28
+ hexToRgba,
29
+ formatNumber
30
+ } = require("../utils");
31
+
32
+ const {
33
+ DEFAULT_COLORS,
34
+ LAYOUT,
35
+ DEFAULT_DIMENSIONS
36
+ } = require("./constants");
37
+
38
+ /**
39
+ * @class FacebookPost
40
+ * @classdesc Gera um banner no estilo de post do Facebook.
41
+ * @example const post = new FacebookPost()
42
+ * .setName("Nome Completo")
43
+ * .setContent("Conteúdo do post")
44
+ * .setImage("imagem.jpg")
45
+ * .setLikes(1500)
46
+ * .setComments(200)
47
+ * .setShares(50)
48
+ * .build();
49
+ */
50
+ module.exports = class FacebookPost {
51
+ constructor(options) {
52
+ // Dados Principais
53
+ this.name = "Nome Completo";
54
+ this.avatar = null;
55
+ this.content = "Conteúdo do post";
56
+ this.image = null;
57
+ this.likes = 0;
58
+ this.comments = 0;
59
+ this.shares = 0;
60
+ this.postTime = "1h";
61
+ this.isVerified = false;
62
+ this.privacy = "public"; // public, friends, private
63
+ this.isPagePost = false;
64
+ this.pageName = null;
65
+ this.pageLogo = null;
66
+ this.reactions = {
67
+ like: 0,
68
+ love: 0,
69
+ care: 0,
70
+ haha: 0,
71
+ wow: 0,
72
+ sad: 0,
73
+ angry: 0
74
+ };
75
+
76
+ // Personalização Visual
77
+ this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
78
+ this.theme = "light"; // light, dark
79
+ this.postType = "standard"; // standard, photo, video, shared, event
80
+ this.cornerRadius = LAYOUT.cornerRadius.small;
81
+
82
+ // Configurações de Layout
83
+ this.cardWidth = DEFAULT_DIMENSIONS.post.width;
84
+ this.cardHeight = 700;
85
+ }
86
+
87
+ // --- Setters para Dados Principais ---
88
+ /**
89
+ * Define o nome completo
90
+ * @param {string} text - Nome completo
91
+ * @returns {FacebookPost} - Instância atual para encadeamento
92
+ */
93
+ setName(text) {
94
+ if (!text || typeof text !== "string") throw new Error("O nome completo deve ser uma string não vazia.");
95
+ this.name = text;
96
+ return this;
97
+ }
98
+
99
+ /**
100
+ * Define o avatar
101
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
102
+ * @returns {FacebookPost} - Instância atual para encadeamento
103
+ */
104
+ setAvatar(image) {
105
+ if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
106
+ this.avatar = image;
107
+ return this;
108
+ }
109
+
110
+ /**
111
+ * Define o conteúdo do post
112
+ * @param {string} text - Texto do conteúdo
113
+ * @returns {FacebookPost} - Instância atual para encadeamento
114
+ */
115
+ setContent(text) {
116
+ if (!text || typeof text !== "string") throw new Error("O conteúdo deve ser uma string não vazia.");
117
+ this.content = text;
118
+ return this;
119
+ }
120
+
121
+ /**
122
+ * Define a imagem principal do post
123
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem
124
+ * @returns {FacebookPost} - Instância atual para encadeamento
125
+ */
126
+ setImage(image) {
127
+ if (!image) throw new Error("A fonte da imagem não pode estar vazia.");
128
+ this.image = image;
129
+ return this;
130
+ }
131
+
132
+ /**
133
+ * Define o número de curtidas
134
+ * @param {number} count - Número de curtidas
135
+ * @returns {FacebookPost} - Instância atual para encadeamento
136
+ */
137
+ setLikes(count) {
138
+ if (typeof count !== "number" || count < 0) throw new Error("O número de curtidas deve ser um número não negativo.");
139
+ this.likes = count;
140
+ return this;
141
+ }
142
+
143
+ /**
144
+ * Define o número de comentários
145
+ * @param {number} count - Número de comentários
146
+ * @returns {FacebookPost} - Instância atual para encadeamento
147
+ */
148
+ setComments(count) {
149
+ if (typeof count !== "number" || count < 0) throw new Error("O número de comentários deve ser um número não negativo.");
150
+ this.comments = count;
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Define o número de compartilhamentos
156
+ * @param {number} count - Número de compartilhamentos
157
+ * @returns {FacebookPost} - Instância atual para encadeamento
158
+ */
159
+ setShares(count) {
160
+ if (typeof count !== "number" || count < 0) throw new Error("O número de compartilhamentos deve ser um número não negativo.");
161
+ this.shares = count;
162
+ return this;
163
+ }
164
+
165
+ /**
166
+ * Define o tempo de publicação
167
+ * @param {string} text - Tempo de publicação (ex: "1h", "2d", "1sem")
168
+ * @returns {FacebookPost} - Instância atual para encadeamento
169
+ */
170
+ setPostTime(text) {
171
+ if (!text || typeof text !== "string") throw new Error("O tempo de publicação deve ser uma string não vazia.");
172
+ this.postTime = text;
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ * Define se o usuário é verificado
178
+ * @param {boolean} isVerified - Se o usuário é verificado
179
+ * @returns {FacebookPost} - Instância atual para encadeamento
180
+ */
181
+ setVerified(isVerified = true) {
182
+ this.isVerified = !!isVerified;
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Define a privacidade do post
188
+ * @param {string} privacy - Privacidade ('public', 'friends', 'private')
189
+ * @returns {FacebookPost} - Instância atual para encadeamento
190
+ */
191
+ setPrivacy(privacy) {
192
+ const validPrivacy = ["public", "friends", "private"];
193
+ if (!privacy || !validPrivacy.includes(privacy.toLowerCase())) {
194
+ throw new Error(`Privacidade inválida. Use uma das seguintes: ${validPrivacy.join(", ")}`);
195
+ }
196
+
197
+ this.privacy = privacy.toLowerCase();
198
+ return this;
199
+ }
200
+
201
+ /**
202
+ * Define se é um post de página
203
+ * @param {boolean} isPagePost - Se é um post de página
204
+ * @param {string} pageName - Nome da página
205
+ * @param {string|Buffer|Object} pageLogo - URL, Buffer ou caminho do logo da página
206
+ * @returns {FacebookPost} - Instância atual para encadeamento
207
+ */
208
+ setPagePost(isPagePost = true, pageName = null, pageLogo = null) {
209
+ this.isPagePost = !!isPagePost;
210
+
211
+ if (isPagePost) {
212
+ if (!pageName || typeof pageName !== "string") {
213
+ throw new Error("O nome da página deve ser uma string não vazia.");
214
+ }
215
+
216
+ this.pageName = pageName;
217
+ this.pageLogo = pageLogo;
218
+ }
219
+
220
+ return this;
221
+ }
222
+
223
+ /**
224
+ * Define as reações do post
225
+ * @param {Object} reactions - Objeto com as contagens de reações
226
+ * @returns {FacebookPost} - Instância atual para encadeamento
227
+ */
228
+ setReactions(reactions) {
229
+ if (!reactions || typeof reactions !== "object") {
230
+ throw new Error("As reações devem ser um objeto.");
231
+ }
232
+
233
+ const validReactions = ["like", "love", "care", "haha", "wow", "sad", "angry"];
234
+
235
+ for (const [key, value] of Object.entries(reactions)) {
236
+ if (validReactions.includes(key)) {
237
+ if (typeof value !== "number" || value < 0) {
238
+ throw new Error(`O valor da reação "${key}" deve ser um número não negativo.`);
239
+ }
240
+
241
+ this.reactions[key] = value;
242
+ }
243
+ }
244
+
245
+ // Atualiza o total de curtidas
246
+ this.likes = Object.values(this.reactions).reduce((a, b) => a + b, 0);
247
+
248
+ return this;
249
+ }
250
+
251
+ // --- Setters para Personalização Visual ---
252
+ /**
253
+ * Define o tema
254
+ * @param {string} theme - Tema ('light', 'dark')
255
+ * @returns {FacebookPost} - Instância atual para encadeamento
256
+ */
257
+ setTheme(theme) {
258
+ const validThemes = ["light", "dark"];
259
+ if (!theme || !validThemes.includes(theme.toLowerCase())) {
260
+ throw new Error(`Tema inválido. Use um dos seguintes: ${validThemes.join(", ")}`);
261
+ }
262
+
263
+ this.theme = theme.toLowerCase();
264
+ return this;
265
+ }
266
+
267
+ /**
268
+ * Define o tipo de post
269
+ * @param {string} type - Tipo de post ('standard', 'photo', 'video', 'shared', 'event')
270
+ * @returns {FacebookPost} - Instância atual para encadeamento
271
+ */
272
+ setPostType(type) {
273
+ const validTypes = ["standard", "photo", "video", "shared", "event"];
274
+ if (!type || !validTypes.includes(type.toLowerCase())) {
275
+ throw new Error(`Tipo de post inválido. Use um dos seguintes: ${validTypes.join(", ")}`);
276
+ }
277
+
278
+ this.postType = type.toLowerCase();
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Define o raio dos cantos arredondados
284
+ * @param {number} radius - Raio dos cantos em pixels
285
+ * @returns {FacebookPost} - Instância atual para encadeamento
286
+ */
287
+ setCornerRadius(radius) {
288
+ if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
289
+ this.cornerRadius = radius;
290
+ return this;
291
+ }
292
+
293
+ /**
294
+ * Define as dimensões do card
295
+ * @param {number} width - Largura do card em pixels
296
+ * @param {number} height - Altura do card em pixels
297
+ * @returns {FacebookPost} - Instância atual para encadeamento
298
+ */
299
+ setCardDimensions(width, height) {
300
+ if (typeof width !== "number" || width < 400 || width > 1200) {
301
+ throw new Error("A largura do card deve estar entre 400 e 1200 pixels.");
302
+ }
303
+
304
+ if (typeof height !== "number" || height < 400 || height > 1200) {
305
+ throw new Error("A altura do card deve estar entre 400 e 1200 pixels.");
306
+ }
307
+
308
+ this.cardWidth = width;
309
+ this.cardHeight = height;
310
+
311
+ return this;
312
+ }
313
+
314
+ // --- Método de Construção ---
315
+ /**
316
+ * Constrói o banner e retorna um buffer de imagem
317
+ * @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
318
+ */
319
+ async build() {
320
+ // --- Registro de Fonte ---
321
+ const registeredFontName = await registerFontIfNeeded(this.font);
322
+
323
+ // --- Configuração do Canvas ---
324
+ const cardWidth = this.cardWidth;
325
+ const cardHeight = this.cardHeight;
326
+ const cornerRadius = this.cornerRadius;
327
+ const padding = 16;
328
+
329
+ const canvas = pureimage.make(cardWidth, cardHeight);
330
+ const ctx = canvas.getContext("2d");
331
+
332
+ // --- Configuração de Cores com base no Tema ---
333
+ const colors = this._getThemeColors();
334
+
335
+ // --- Desenha Plano de Fundo ---
336
+ ctx.fillStyle = colors.background;
337
+ roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
338
+
339
+ // --- Desenha Cabeçalho do Post ---
340
+ const headerHeight = 70;
341
+
342
+ // Avatar
343
+ const avatarSize = 50;
344
+ const avatarX = padding;
345
+ const avatarY = padding;
346
+
347
+ ctx.save();
348
+ ctx.beginPath();
349
+ ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
350
+ ctx.closePath();
351
+ ctx.clip();
352
+
353
+ try {
354
+ const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
355
+ ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
356
+ } catch (e) {
357
+ console.error("Falha ao desenhar avatar:", e.message);
358
+
359
+ // Avatar de fallback
360
+ ctx.fillStyle = "#1877F2";
361
+ ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
362
+
363
+ ctx.fillStyle = "#FFFFFF";
364
+ ctx.font = `bold ${avatarSize / 3}px ${registeredFontName}-Bold`;
365
+ ctx.textAlign = "center";
366
+ ctx.textBaseline = "middle";
367
+ ctx.fillText(this.name.charAt(0).toUpperCase(), avatarX + avatarSize / 2, avatarY + avatarSize / 2);
368
+ }
369
+
370
+ ctx.restore();
371
+
372
+ // Informações do usuário
373
+ const infoX = avatarX + avatarSize + 10;
374
+ let infoY = avatarY + 5;
375
+
376
+ // Nome
377
+ ctx.fillStyle = colors.text;
378
+ ctx.font = `bold 16px ${registeredFontName}-Bold`;
379
+ ctx.textAlign = "left";
380
+ ctx.textBaseline = "top";
381
+
382
+ const nameText = this.name;
383
+ const nameWidth = ctx.measureText(nameText).width;
384
+ ctx.fillText(nameText, infoX, infoY);
385
+
386
+ // Ícone de verificado (se aplicável)
387
+ if (this.isVerified) {
388
+ const verifiedSize = 16;
389
+ const verifiedX = infoX + nameWidth + 5;
390
+
391
+ ctx.fillStyle = "#1877F2";
392
+ ctx.beginPath();
393
+ ctx.arc(verifiedX + verifiedSize / 2, infoY + verifiedSize / 2, verifiedSize / 2, 0, Math.PI * 2);
394
+ ctx.fill();
395
+
396
+ ctx.fillStyle = "#FFFFFF";
397
+ ctx.font = `bold 12px ${registeredFontName}-Bold`;
398
+ ctx.textAlign = "center";
399
+ ctx.fillText("✓", verifiedX + verifiedSize / 2, infoY + verifiedSize / 2);
400
+ }
401
+
402
+ // Página (se aplicável)
403
+ if (this.isPagePost) {
404
+ infoY += 20;
405
+ ctx.fillStyle = colors.textSecondary;
406
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
407
+ ctx.textAlign = "left";
408
+ ctx.fillText(this.pageName, infoX, infoY);
409
+ }
410
+
411
+ // Tempo e privacidade
412
+ infoY = this.isPagePost ? infoY + 20 : infoY + 25;
413
+ ctx.fillStyle = colors.textSecondary;
414
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
415
+ ctx.textAlign = "left";
416
+
417
+ let privacyIcon = "🌎";
418
+ if (this.privacy === "friends") {
419
+ privacyIcon = "👥";
420
+ } else if (this.privacy === "private") {
421
+ privacyIcon = "🔒";
422
+ }
423
+
424
+ ctx.fillText(`${this.postTime} • ${privacyIcon}`, infoX, infoY);
425
+
426
+ // Botão de mais opções
427
+ const moreButtonX = cardWidth - padding - 20;
428
+ const moreButtonY = padding + 25;
429
+
430
+ ctx.fillStyle = colors.textSecondary;
431
+ ctx.textAlign = "center";
432
+ ctx.textBaseline = "middle";
433
+ ctx.fillText("•••", moreButtonX, moreButtonY);
434
+
435
+ // --- Desenha Conteúdo do Post ---
436
+ let contentY = headerHeight + padding;
437
+
438
+ // Texto do post
439
+ if (this.content) {
440
+ ctx.fillStyle = colors.text;
441
+ ctx.font = `regular 16px ${registeredFontName}-Regular`;
442
+ ctx.textAlign = "left";
443
+ ctx.textBaseline = "top";
444
+
445
+ contentY = wrapText(ctx, this.content, padding, contentY, cardWidth - padding * 2, 24, registeredFontName);
446
+ contentY += padding;
447
+ }
448
+
449
+ // Imagem (se fornecida)
450
+ if (this.image) {
451
+ try {
452
+ const imageHeight = 300;
453
+ const imageY = contentY;
454
+
455
+ ctx.save();
456
+
457
+ // Recorta a imagem com cantos arredondados
458
+ roundRect(ctx, padding, imageY, cardWidth - padding * 2, imageHeight, cornerRadius, false, false);
459
+ ctx.clip();
460
+
461
+ const img = await loadImageWithAxios(this.image);
462
+ const aspect = img.width / img.height;
463
+ const imageWidth = cardWidth - padding * 2;
464
+
465
+ // Ajusta as dimensões para manter a proporção
466
+ let drawWidth = imageWidth;
467
+ let drawHeight = imageWidth / aspect;
468
+
469
+ if (drawHeight > imageHeight) {
470
+ drawHeight = imageHeight;
471
+ drawWidth = imageHeight * aspect;
472
+ }
473
+
474
+ const offsetX = padding + (imageWidth - drawWidth) / 2;
475
+ const offsetY = imageY + (imageHeight - drawHeight) / 2;
476
+
477
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
478
+
479
+ ctx.restore();
480
+
481
+ contentY = imageY + imageHeight + padding;
482
+ } catch (e) {
483
+ console.error("Falha ao desenhar imagem:", e.message);
484
+ }
485
+ }
486
+
487
+ // --- Desenha Contadores de Reações ---
488
+ const reactionsY = contentY;
489
+ const reactionsHeight = 30;
490
+
491
+ // Ícones de reações
492
+ const reactionIcons = {
493
+ like: "👍",
494
+ love: "❤️",
495
+ care: "🤗",
496
+ haha: "😄",
497
+ wow: "😮",
498
+ sad: "😢",
499
+ angry: "😠"
500
+ };
501
+
502
+ // Desenha ícones de reações
503
+ let hasReactions = false;
504
+ const activeReactions = Object.entries(this.reactions).filter(([_, count]) => count > 0);
505
+
506
+ if (activeReactions.length > 0) {
507
+ hasReactions = true;
508
+
509
+ // Fundo dos ícones
510
+ ctx.fillStyle = colors.reactionBackground;
511
+ roundRect(ctx, padding, reactionsY, 80, reactionsHeight, reactionsHeight / 2, true, false);
512
+
513
+ // Desenha até 3 ícones de reações
514
+ const iconSize = 20;
515
+ const iconSpacing = 15;
516
+ let iconX = padding + 10;
517
+
518
+ activeReactions.slice(0, 3).forEach(([reaction, _]) => {
519
+ ctx.fillStyle = colors.text;
520
+ ctx.font = `regular 16px ${registeredFontName}-Regular`;
521
+ ctx.textAlign = "center";
522
+ ctx.textBaseline = "middle";
523
+ ctx.fillText(reactionIcons[reaction], iconX, reactionsY + reactionsHeight / 2);
524
+
525
+ iconX += iconSpacing;
526
+ });
527
+
528
+ // Contador de reações
529
+ ctx.fillStyle = colors.textSecondary;
530
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
531
+ ctx.textAlign = "left";
532
+ ctx.textBaseline = "middle";
533
+ ctx.fillText(formatNumber(this.likes), padding + 65, reactionsY + reactionsHeight / 2);
534
+
535
+ // Contadores de comentários e compartilhamentos
536
+ ctx.textAlign = "right";
537
+
538
+ let counterText = "";
539
+ if (this.comments > 0 && this.shares > 0) {
540
+ counterText = `${formatNumber(this.comments)} comentários • ${formatNumber(this.shares)} compartilhamentos`;
541
+ } else if (this.comments > 0) {
542
+ counterText = `${formatNumber(this.comments)} comentários`;
543
+ } else if (this.shares > 0) {
544
+ counterText = `${formatNumber(this.shares)} compartilhamentos`;
545
+ }
546
+
547
+ if (counterText) {
548
+ ctx.fillText(counterText, cardWidth - padding, reactionsY + reactionsHeight / 2);
549
+ }
550
+
551
+ contentY = reactionsY + reactionsHeight + padding;
552
+ }
553
+
554
+ // --- Desenha Barra de Interações ---
555
+ const interactionBarY = contentY;
556
+ const interactionBarHeight = 50;
557
+
558
+ // Linha separadora
559
+ ctx.strokeStyle = colors.separator;
560
+ ctx.lineWidth = 1;
561
+ ctx.beginPath();
562
+ ctx.moveTo(padding, interactionBarY);
563
+ ctx.lineTo(cardWidth - padding, interactionBarY);
564
+ ctx.stroke();
565
+
566
+ // Ícones de interação
567
+ const iconSpacing = (cardWidth - padding * 2) / 3;
568
+ const iconY = interactionBarY + interactionBarHeight / 2;
569
+
570
+ // Ícone de curtida
571
+ ctx.fillStyle = colors.textSecondary;
572
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
573
+ ctx.textAlign = "center";
574
+ ctx.textBaseline = "middle";
575
+
576
+ ctx.fillText("👍 Curtir", padding + iconSpacing / 2, iconY);
577
+
578
+ // Ícone de comentário
579
+ ctx.fillText("💬 Comentar", padding + iconSpacing * 1.5, iconY);
580
+
581
+ // Ícone de compartilhamento
582
+ ctx.fillText("↗ Compartilhar", padding + iconSpacing * 2.5, iconY);
583
+
584
+ // --- Desenha Caixa de Comentário ---
585
+ const commentBoxY = interactionBarY + interactionBarHeight + padding;
586
+ const commentBoxHeight = 60;
587
+
588
+ if (commentBoxY + commentBoxHeight <= cardHeight - padding) {
589
+ // Linha separadora
590
+ ctx.strokeStyle = colors.separator;
591
+ ctx.lineWidth = 1;
592
+ ctx.beginPath();
593
+ ctx.moveTo(padding, commentBoxY);
594
+ ctx.lineTo(cardWidth - padding, commentBoxY);
595
+ ctx.stroke();
596
+
597
+ // Avatar do usuário
598
+ const commentAvatarSize = 40;
599
+ const commentAvatarX = padding;
600
+ const commentAvatarY = commentBoxY + padding;
601
+
602
+ ctx.save();
603
+ ctx.beginPath();
604
+ ctx.arc(commentAvatarX + commentAvatarSize / 2, commentAvatarY + commentAvatarSize / 2, commentAvatarSize / 2, 0, Math.PI * 2);
605
+ ctx.closePath();
606
+ ctx.clip();
607
+
608
+ try {
609
+ const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
610
+ ctx.drawImage(avatarImg, commentAvatarX, commentAvatarY, commentAvatarSize, commentAvatarSize);
611
+ } catch (e) {
612
+ console.error("Falha ao desenhar avatar de comentário:", e.message);
613
+
614
+ // Avatar de fallback
615
+ ctx.fillStyle = "#1877F2";
616
+ ctx.fillRect(commentAvatarX, commentAvatarY, commentAvatarSize, commentAvatarSize);
617
+
618
+ ctx.fillStyle = "#FFFFFF";
619
+ ctx.font = `bold ${commentAvatarSize / 3}px ${registeredFontName}-Bold`;
620
+ ctx.textAlign = "center";
621
+ ctx.textBaseline = "middle";
622
+ ctx.fillText(this.name.charAt(0).toUpperCase(), commentAvatarX + commentAvatarSize / 2, commentAvatarY + commentAvatarSize / 2);
623
+ }
624
+
625
+ ctx.restore();
626
+
627
+ // Caixa de comentário
628
+ const commentBoxWidth = cardWidth - padding * 2 - commentAvatarSize - 10;
629
+ const commentBoxX = commentAvatarX + commentAvatarSize + 10;
630
+
631
+ ctx.fillStyle = colors.commentBackground;
632
+ roundRect(ctx, commentBoxX, commentAvatarY, commentBoxWidth, commentAvatarSize, commentAvatarSize / 2, true, false);
633
+
634
+ ctx.fillStyle = colors.textSecondary;
635
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
636
+ ctx.textAlign = "left";
637
+ ctx.textBaseline = "middle";
638
+ ctx.fillText("Escreva um comentário...", commentBoxX + 15, commentAvatarY + commentAvatarSize / 2);
639
+ }
640
+
641
+ // --- Codifica e Retorna Buffer ---
642
+ try {
643
+ return await encodeToBuffer(canvas);
644
+ } catch (err) {
645
+ console.error("Falha ao codificar o Post do Facebook:", err);
646
+ throw new Error("Não foi possível gerar o buffer de imagem do Post do Facebook.");
647
+ }
648
+ }
649
+
650
+ // --- Métodos Auxiliares Privados ---
651
+ /**
652
+ * Obtém as cores com base no tema selecionado
653
+ * @private
654
+ */
655
+ _getThemeColors() {
656
+ switch (this.theme) {
657
+ case "dark":
658
+ return {
659
+ background: "#242526",
660
+ text: "#E4E6EB",
661
+ textSecondary: "#B0B3B8",
662
+ separator: "#3E4042",
663
+ reactionBackground: "#3A3B3C",
664
+ commentBackground: "#3A3B3C"
665
+ };
666
+ case "light":
667
+ default:
668
+ return {
669
+ background: "#FFFFFF",
670
+ text: "#050505",
671
+ textSecondary: "#65676B",
672
+ separator: "#CED0D4",
673
+ reactionBackground: "#E4E6EB",
674
+ commentBackground: "#F0F2F5"
675
+ };
676
+ }
677
+ }
678
+ };
679
+