@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,604 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Módulo de Banner de Cabeçalho do Twitter
5
+ *
6
+ * Este módulo gera banners no estilo de cabeçalho de perfil do Twitter com
7
+ * imagem de capa, avatar, nome, username, bio e estatísticas.
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 TwitterHeader
40
+ * @classdesc Gera um banner no estilo de cabeçalho de perfil do Twitter.
41
+ * @example const header = new TwitterHeader()
42
+ * .setName("Nome Completo")
43
+ * .setUsername("usuario")
44
+ * .setBio("Desenvolvedor | Designer | Criador de conteúdo")
45
+ * .setAvatar("avatar.png")
46
+ * .setCoverImage("cover.jpg")
47
+ * .setFollowers(5200)
48
+ * .setFollowing(420)
49
+ * .setVerified(true)
50
+ * .build();
51
+ */
52
+ module.exports = class TwitterHeader {
53
+ constructor(options) {
54
+ // Dados Principais
55
+ this.name = "Nome Completo";
56
+ this.username = "usuario";
57
+ this.bio = null;
58
+ this.location = null;
59
+ this.website = null;
60
+ this.joinDate = null;
61
+ this.avatar = null;
62
+ this.coverImage = null;
63
+ this.followers = 0;
64
+ this.following = 0;
65
+ this.tweets = 0;
66
+ this.isVerified = false;
67
+ this.isPremium = false;
68
+
69
+ // Personalização Visual
70
+ this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
71
+ this.theme = "light"; // light, dark, dim
72
+ this.accentColor = DEFAULT_COLORS.twitter.primary;
73
+ this.useTextShadow = false;
74
+ this.useDarkMode = false;
75
+
76
+ // Configurações de Layout
77
+ this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
78
+ this.cardHeight = 400;
79
+ this.avatarSize = 120;
80
+ }
81
+
82
+ // --- Setters para Dados Principais ---
83
+ /**
84
+ * Define o nome completo
85
+ * @param {string} text - Nome completo
86
+ * @returns {TwitterHeader} - Instância atual para encadeamento
87
+ */
88
+ setName(text) {
89
+ if (!text || typeof text !== "string") throw new Error("O nome completo deve ser uma string não vazia.");
90
+ this.name = text;
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Define o nome de usuário
96
+ * @param {string} text - Nome de usuário (sem @)
97
+ * @returns {TwitterHeader} - Instância atual para encadeamento
98
+ */
99
+ setUsername(text) {
100
+ if (!text || typeof text !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
101
+ this.username = text.replace(/^@/, ''); // Remove @ se presente
102
+ return this;
103
+ }
104
+
105
+ /**
106
+ * Define a bio
107
+ * @param {string} text - Texto da bio
108
+ * @returns {TwitterHeader} - Instância atual para encadeamento
109
+ */
110
+ setBio(text) {
111
+ if (!text || typeof text !== "string") throw new Error("A bio deve ser uma string não vazia.");
112
+ this.bio = text;
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Define a localização
118
+ * @param {string} text - Texto da localização
119
+ * @returns {TwitterHeader} - Instância atual para encadeamento
120
+ */
121
+ setLocation(text) {
122
+ if (!text || typeof text !== "string") throw new Error("A localização deve ser uma string não vazia.");
123
+ this.location = text;
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * Define o website
129
+ * @param {string} text - URL do website
130
+ * @returns {TwitterHeader} - Instância atual para encadeamento
131
+ */
132
+ setWebsite(text) {
133
+ if (!text || typeof text !== "string") throw new Error("O website deve ser uma string não vazia.");
134
+ this.website = text;
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * Define a data de entrada
140
+ * @param {string} text - Data de entrada (ex: "Entrou em maio de 2020")
141
+ * @returns {TwitterHeader} - Instância atual para encadeamento
142
+ */
143
+ setJoinDate(text) {
144
+ if (!text || typeof text !== "string") throw new Error("A data de entrada deve ser uma string não vazia.");
145
+ this.joinDate = text;
146
+ return this;
147
+ }
148
+
149
+ /**
150
+ * Define o avatar
151
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
152
+ * @returns {TwitterHeader} - Instância atual para encadeamento
153
+ */
154
+ setAvatar(image) {
155
+ if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
156
+ this.avatar = image;
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Define a imagem de capa
162
+ * @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem de capa
163
+ * @returns {TwitterHeader} - Instância atual para encadeamento
164
+ */
165
+ setCoverImage(image) {
166
+ if (!image) throw new Error("A fonte da imagem de capa não pode estar vazia.");
167
+ this.coverImage = image;
168
+ return this;
169
+ }
170
+
171
+ /**
172
+ * Define o número de seguidores
173
+ * @param {number} count - Número de seguidores
174
+ * @returns {TwitterHeader} - Instância atual para encadeamento
175
+ */
176
+ setFollowers(count) {
177
+ if (typeof count !== "number" || count < 0) throw new Error("O número de seguidores deve ser um número não negativo.");
178
+ this.followers = count;
179
+ return this;
180
+ }
181
+
182
+ /**
183
+ * Define o número de pessoas seguidas
184
+ * @param {number} count - Número de pessoas seguidas
185
+ * @returns {TwitterHeader} - Instância atual para encadeamento
186
+ */
187
+ setFollowing(count) {
188
+ if (typeof count !== "number" || count < 0) throw new Error("O número de pessoas seguidas deve ser um número não negativo.");
189
+ this.following = count;
190
+ return this;
191
+ }
192
+
193
+ /**
194
+ * Define o número de tweets
195
+ * @param {number} count - Número de tweets
196
+ * @returns {TwitterHeader} - Instância atual para encadeamento
197
+ */
198
+ setTweets(count) {
199
+ if (typeof count !== "number" || count < 0) throw new Error("O número de tweets deve ser um número não negativo.");
200
+ this.tweets = count;
201
+ return this;
202
+ }
203
+
204
+ /**
205
+ * Define se o perfil é verificado
206
+ * @param {boolean} isVerified - Se o perfil é verificado
207
+ * @returns {TwitterHeader} - Instância atual para encadeamento
208
+ */
209
+ setVerified(isVerified = true) {
210
+ this.isVerified = !!isVerified;
211
+ return this;
212
+ }
213
+
214
+ /**
215
+ * Define se o perfil é premium
216
+ * @param {boolean} isPremium - Se o perfil é premium
217
+ * @returns {TwitterHeader} - Instância atual para encadeamento
218
+ */
219
+ setPremium(isPremium = true) {
220
+ this.isPremium = !!isPremium;
221
+ return this;
222
+ }
223
+
224
+ // --- Setters para Personalização Visual ---
225
+ /**
226
+ * Define o tema
227
+ * @param {string} theme - Tema ('light', 'dark', 'dim')
228
+ * @returns {TwitterHeader} - Instância atual para encadeamento
229
+ */
230
+ setTheme(theme) {
231
+ const validThemes = ["light", "dark", "dim"];
232
+ if (!theme || !validThemes.includes(theme.toLowerCase())) {
233
+ throw new Error(`Tema inválido. Use um dos seguintes: ${validThemes.join(", ")}`);
234
+ }
235
+
236
+ this.theme = theme.toLowerCase();
237
+ return this;
238
+ }
239
+
240
+ /**
241
+ * Define a cor de destaque
242
+ * @param {string} color - Cor hexadecimal
243
+ * @returns {TwitterHeader} - Instância atual para encadeamento
244
+ */
245
+ setAccentColor(color) {
246
+ if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
247
+ this.accentColor = color;
248
+ return this;
249
+ }
250
+
251
+ /**
252
+ * Ativa ou desativa a sombra de texto
253
+ * @param {boolean} enabled - Se a sombra de texto deve ser ativada
254
+ * @returns {TwitterHeader} - Instância atual para encadeamento
255
+ */
256
+ enableTextShadow(enabled = true) {
257
+ this.useTextShadow = enabled;
258
+ return this;
259
+ }
260
+
261
+ /**
262
+ * Ativa ou desativa o modo escuro
263
+ * @param {boolean} enabled - Se o modo escuro deve ser ativado
264
+ * @returns {TwitterHeader} - Instância atual para encadeamento
265
+ */
266
+ enableDarkMode(enabled = true) {
267
+ this.useDarkMode = enabled;
268
+ this.theme = enabled ? "dark" : "light";
269
+ return this;
270
+ }
271
+
272
+ /**
273
+ * Define as dimensões do card
274
+ * @param {number} width - Largura do card em pixels
275
+ * @param {number} height - Altura do card em pixels
276
+ * @returns {TwitterHeader} - Instância atual para encadeamento
277
+ */
278
+ setCardDimensions(width, height) {
279
+ if (typeof width !== "number" || width < 600 || width > 1920) {
280
+ throw new Error("A largura do card deve estar entre 600 e 1920 pixels.");
281
+ }
282
+
283
+ if (typeof height !== "number" || height < 300 || height > 800) {
284
+ throw new Error("A altura do card deve estar entre 300 e 800 pixels.");
285
+ }
286
+
287
+ this.cardWidth = width;
288
+ this.cardHeight = height;
289
+
290
+ return this;
291
+ }
292
+
293
+ /**
294
+ * Define o tamanho do avatar
295
+ * @param {number} size - Tamanho do avatar em pixels
296
+ * @returns {TwitterHeader} - Instância atual para encadeamento
297
+ */
298
+ setAvatarSize(size) {
299
+ if (typeof size !== "number" || size < 80 || size > 200) {
300
+ throw new Error("O tamanho do avatar deve estar entre 80 e 200 pixels.");
301
+ }
302
+
303
+ this.avatarSize = size;
304
+ return this;
305
+ }
306
+
307
+ // --- Método de Construção ---
308
+ /**
309
+ * Constrói o banner e retorna um buffer de imagem
310
+ * @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
311
+ */
312
+ async build() {
313
+ // --- Registro de Fonte ---
314
+ const registeredFontName = await registerFontIfNeeded(this.font);
315
+
316
+ // --- Configuração do Canvas ---
317
+ const cardWidth = this.cardWidth;
318
+ const cardHeight = this.cardHeight;
319
+ const coverHeight = cardHeight * 0.5;
320
+ const avatarSize = this.avatarSize;
321
+ const padding = 20;
322
+
323
+ const canvas = pureimage.make(cardWidth, cardHeight);
324
+ const ctx = canvas.getContext("2d");
325
+
326
+ // --- Configuração de Cores com base no Tema ---
327
+ const colors = this._getThemeColors();
328
+
329
+ // --- Desenha Plano de Fundo ---
330
+ ctx.fillStyle = colors.background;
331
+ ctx.fillRect(0, 0, cardWidth, cardHeight);
332
+
333
+ // --- Desenha Imagem de Capa ---
334
+ if (this.coverImage) {
335
+ try {
336
+ const img = await loadImageWithAxios(this.coverImage);
337
+ const aspect = img.width / img.height;
338
+ let drawWidth = cardWidth;
339
+ let drawHeight = cardWidth / aspect;
340
+
341
+ // Ajusta as dimensões para cobrir toda a área da capa
342
+ if (drawHeight < coverHeight) {
343
+ drawHeight = coverHeight;
344
+ drawWidth = coverHeight * aspect;
345
+ }
346
+
347
+ const offsetX = (cardWidth - drawWidth) / 2;
348
+ const offsetY = 0;
349
+
350
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, coverHeight);
351
+ } catch (e) {
352
+ console.error("Falha ao desenhar imagem de capa:", e.message);
353
+
354
+ // Capa de fallback
355
+ ctx.fillStyle = this.accentColor;
356
+ ctx.fillRect(0, 0, cardWidth, coverHeight);
357
+ }
358
+ } else {
359
+ // Capa de fallback
360
+ ctx.fillStyle = this.accentColor;
361
+ ctx.fillRect(0, 0, cardWidth, coverHeight);
362
+ }
363
+
364
+ // --- Desenha Avatar ---
365
+ const avatarX = padding;
366
+ const avatarY = coverHeight - avatarSize / 2;
367
+
368
+ ctx.save();
369
+ ctx.beginPath();
370
+ ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
371
+ ctx.closePath();
372
+ ctx.clip();
373
+
374
+ try {
375
+ const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
376
+ ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
377
+ } catch (e) {
378
+ console.error("Falha ao desenhar avatar:", e.message);
379
+
380
+ // Avatar de fallback
381
+ ctx.fillStyle = colors.secondary;
382
+ ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
383
+
384
+ ctx.fillStyle = colors.text;
385
+ ctx.font = `bold ${avatarSize / 3}px ${registeredFontName}-Bold`;
386
+ ctx.textAlign = "center";
387
+ ctx.textBaseline = "middle";
388
+ ctx.fillText(this.name.charAt(0).toUpperCase(), avatarX + avatarSize / 2, avatarY + avatarSize / 2);
389
+ }
390
+
391
+ ctx.restore();
392
+
393
+ // Borda do avatar
394
+ ctx.strokeStyle = colors.background;
395
+ ctx.lineWidth = 4;
396
+ ctx.beginPath();
397
+ ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + ctx.lineWidth / 2, 0, Math.PI * 2);
398
+ ctx.stroke();
399
+ ctx.closePath();
400
+
401
+ // --- Desenha Botão de Seguir ---
402
+ const followButtonWidth = 120;
403
+ const followButtonHeight = 40;
404
+ const followButtonX = cardWidth - followButtonWidth - padding;
405
+ const followButtonY = coverHeight + padding;
406
+
407
+ // Fundo do botão
408
+ ctx.fillStyle = colors.text;
409
+ roundRect(ctx, followButtonX, followButtonY, followButtonWidth, followButtonHeight, 20, true, false);
410
+
411
+ // Texto do botão
412
+ ctx.fillStyle = colors.background;
413
+ ctx.font = `bold 16px ${registeredFontName}-Bold`;
414
+ ctx.textAlign = "center";
415
+ ctx.textBaseline = "middle";
416
+ ctx.fillText("Seguir", followButtonX + followButtonWidth / 2, followButtonY + followButtonHeight / 2);
417
+
418
+ // --- Desenha Nome e Username ---
419
+ const nameX = padding;
420
+ const nameY = coverHeight + avatarSize / 2 + padding;
421
+
422
+ // Nome
423
+ ctx.fillStyle = colors.text;
424
+ ctx.font = `bold 24px ${registeredFontName}-Bold`;
425
+ ctx.textAlign = "left";
426
+ ctx.textBaseline = "top";
427
+
428
+ // Aplica sombra de texto se ativada
429
+ if (this.useTextShadow) {
430
+ applyTextShadow(ctx, hexToRgba(colors.shadow, 0.5), 2, 1, 1);
431
+ }
432
+
433
+ const nameText = this.name;
434
+ const nameWidth = ctx.measureText(nameText).width;
435
+ ctx.fillText(nameText, nameX, nameY);
436
+
437
+ // Desenha ícone de verificado (se aplicável)
438
+ if (this.isVerified) {
439
+ const verifiedSize = 20;
440
+ const verifiedX = nameX + nameWidth + 5;
441
+
442
+ ctx.fillStyle = this.isPremium ? "#FFD700" : this.accentColor;
443
+ ctx.beginPath();
444
+ ctx.arc(verifiedX + verifiedSize / 2, nameY + 12, verifiedSize / 2, 0, Math.PI * 2);
445
+ ctx.fill();
446
+
447
+ ctx.fillStyle = "#FFFFFF";
448
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
449
+ ctx.textAlign = "center";
450
+ ctx.fillText("✓", verifiedX + verifiedSize / 2, nameY + 12);
451
+ }
452
+
453
+ // Remove sombra para o próximo texto
454
+ if (this.useTextShadow) {
455
+ clearShadow(ctx);
456
+ }
457
+
458
+ // Username
459
+ ctx.fillStyle = colors.textSecondary;
460
+ ctx.font = `regular 16px ${registeredFontName}-Regular`;
461
+ ctx.textAlign = "left";
462
+ ctx.fillText(`@${this.username}`, nameX, nameY + 30);
463
+
464
+ // --- Desenha Bio ---
465
+ if (this.bio) {
466
+ ctx.fillStyle = colors.text;
467
+ ctx.font = `regular 16px ${registeredFontName}-Regular`;
468
+ ctx.textAlign = "left";
469
+
470
+ const bioY = nameY + 60;
471
+ wrapText(ctx, this.bio, nameX, bioY, cardWidth - padding * 2, 20, registeredFontName);
472
+ }
473
+
474
+ // --- Desenha Informações Adicionais ---
475
+ const infoY = nameY + (this.bio ? 120 : 70);
476
+ let infoX = nameX;
477
+ const infoSpacing = 20;
478
+
479
+ // Localização
480
+ if (this.location) {
481
+ ctx.fillStyle = colors.textSecondary;
482
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
483
+ ctx.textAlign = "left";
484
+
485
+ const locationText = `📍 ${this.location}`;
486
+ ctx.fillText(locationText, infoX, infoY);
487
+
488
+ infoX += ctx.measureText(locationText).width + infoSpacing;
489
+ }
490
+
491
+ // Website
492
+ if (this.website) {
493
+ ctx.fillStyle = this.accentColor;
494
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
495
+ ctx.textAlign = "left";
496
+
497
+ const websiteText = `🔗 ${this.website}`;
498
+ ctx.fillText(websiteText, infoX, infoY);
499
+
500
+ infoX += ctx.measureText(websiteText).width + infoSpacing;
501
+ }
502
+
503
+ // Data de entrada
504
+ if (this.joinDate) {
505
+ ctx.fillStyle = colors.textSecondary;
506
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
507
+ ctx.textAlign = "left";
508
+
509
+ const joinDateText = `🗓️ ${this.joinDate}`;
510
+ ctx.fillText(joinDateText, infoX, infoY);
511
+ }
512
+
513
+ // --- Desenha Estatísticas ---
514
+ const statsY = infoY + 30;
515
+ let statsX = nameX;
516
+ const statsSpacing = 30;
517
+
518
+ // Seguidores
519
+ ctx.fillStyle = colors.text;
520
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
521
+ ctx.textAlign = "left";
522
+
523
+ const followersText = `${formatNumber(this.followers)}`;
524
+ ctx.fillText(followersText, statsX, statsY);
525
+
526
+ ctx.fillStyle = colors.textSecondary;
527
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
528
+ const followersLabelWidth = ctx.measureText(" Seguidores").width;
529
+ ctx.fillText(" Seguidores", statsX + ctx.measureText(followersText).width, statsY);
530
+
531
+ statsX += ctx.measureText(followersText).width + followersLabelWidth + statsSpacing;
532
+
533
+ // Seguindo
534
+ ctx.fillStyle = colors.text;
535
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
536
+
537
+ const followingText = `${formatNumber(this.following)}`;
538
+ ctx.fillText(followingText, statsX, statsY);
539
+
540
+ ctx.fillStyle = colors.textSecondary;
541
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
542
+ const followingLabelWidth = ctx.measureText(" Seguindo").width;
543
+ ctx.fillText(" Seguindo", statsX + ctx.measureText(followingText).width, statsY);
544
+
545
+ statsX += ctx.measureText(followingText).width + followingLabelWidth + statsSpacing;
546
+
547
+ // Tweets
548
+ if (this.tweets > 0) {
549
+ ctx.fillStyle = colors.text;
550
+ ctx.font = `bold 14px ${registeredFontName}-Bold`;
551
+
552
+ const tweetsText = `${formatNumber(this.tweets)}`;
553
+ ctx.fillText(tweetsText, statsX, statsY);
554
+
555
+ ctx.fillStyle = colors.textSecondary;
556
+ ctx.font = `regular 14px ${registeredFontName}-Regular`;
557
+ ctx.fillText(" Tweets", statsX + ctx.measureText(tweetsText).width, statsY);
558
+ }
559
+
560
+ // --- Codifica e Retorna Buffer ---
561
+ try {
562
+ return await encodeToBuffer(canvas);
563
+ } catch (err) {
564
+ console.error("Falha ao codificar o Cabeçalho do Twitter:", err);
565
+ throw new Error("Não foi possível gerar o buffer de imagem do Cabeçalho do Twitter.");
566
+ }
567
+ }
568
+
569
+ // --- Métodos Auxiliares Privados ---
570
+ /**
571
+ * Obtém as cores com base no tema selecionado
572
+ * @private
573
+ */
574
+ _getThemeColors() {
575
+ switch (this.theme) {
576
+ case "dark":
577
+ return {
578
+ background: "#000000",
579
+ secondary: "#16181c",
580
+ text: "#FFFFFF",
581
+ textSecondary: "#8B98A5",
582
+ shadow: "#000000"
583
+ };
584
+ case "dim":
585
+ return {
586
+ background: "#15202B",
587
+ secondary: "#1E2732",
588
+ text: "#FFFFFF",
589
+ textSecondary: "#8B98A5",
590
+ shadow: "#10171E"
591
+ };
592
+ case "light":
593
+ default:
594
+ return {
595
+ background: "#FFFFFF",
596
+ secondary: "#F7F9FA",
597
+ text: "#0F1419",
598
+ textSecondary: "#536471",
599
+ shadow: "#CCCCCC"
600
+ };
601
+ }
602
+ }
603
+ };
604
+