fuelcard 1.0.0

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.
@@ -0,0 +1,224 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Flame Theme ║
4
+ * ║ Premium theme with Fuelcard mascot ║
5
+ * ╚═══════════════════════════════════════════════════════════════╝
6
+ */
7
+
8
+ import { createCanvas, loadImage } from '@napi-rs/canvas';
9
+ import { cropImage } from 'cropify';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ // Path to mascot image
17
+ const MASCOT_PATH = path.join(__dirname, '../../assets/mascot.png');
18
+
19
+ /**
20
+ * @typedef {Object} FlameOptions
21
+ * @property {string|Buffer} thumbnail - Album art URL or buffer
22
+ * @property {string} [trackName='Unknown Track'] - Song title
23
+ * @property {string} [artistName='Unknown Artist'] - Artist name
24
+ * @property {string} [requester] - Who requested the track
25
+ * @property {number} [progress=0] - Progress percentage (0-100)
26
+ * @property {string} [startTime='0:00'] - Current timestamp
27
+ * @property {string} [endTime='0:00'] - Total duration
28
+ * @property {boolean} [showMascot=true] - Show the Fuelcard mascot
29
+ */
30
+
31
+ /**
32
+ * Generate a flame-styled music card with optional mascot
33
+ * @param {FlameOptions} options
34
+ * @returns {Promise<Buffer>}
35
+ */
36
+ export const Flame = async ({
37
+ thumbnail,
38
+ trackName = 'Unknown Track',
39
+ artistName = 'Unknown Artist',
40
+ requester = '',
41
+ progress = 0,
42
+ startTime = '0:00',
43
+ endTime = '0:00',
44
+ showMascot = true
45
+ }) => {
46
+ const width = 900;
47
+ const height = 280;
48
+
49
+ // Flame theme colors
50
+ const primaryColor = '#ff6b00'; // Orange
51
+ const secondaryColor = '#ffd700'; // Gold
52
+ const bgColor = '#0a0a0a';
53
+
54
+ // Truncate text
55
+ if (trackName.length > 32) trackName = trackName.substring(0, 29) + '...';
56
+ if (artistName.length > 35) artistName = artistName.substring(0, 32) + '...';
57
+ if (requester && requester.length > 20) requester = requester.substring(0, 17) + '...';
58
+
59
+ const safeProgress = Math.min(Math.max(progress, 0), 100);
60
+ const progressWidth = (safeProgress / 100) * 450;
61
+
62
+ // Create canvas
63
+ const canvas = createCanvas(width, height);
64
+ const ctx = canvas.getContext('2d');
65
+
66
+ // Dark background with subtle gradient
67
+ const gradient = ctx.createLinearGradient(0, 0, width, height);
68
+ gradient.addColorStop(0, bgColor);
69
+ gradient.addColorStop(0.5, '#111111');
70
+ gradient.addColorStop(1, '#0a0a0a');
71
+
72
+ ctx.fillStyle = gradient;
73
+ ctx.beginPath();
74
+ ctx.roundRect(0, 0, width, height, 20);
75
+ ctx.fill();
76
+
77
+ // Flame gradient border
78
+ const borderGradient = ctx.createLinearGradient(0, 0, width, 0);
79
+ borderGradient.addColorStop(0, primaryColor);
80
+ borderGradient.addColorStop(0.5, secondaryColor);
81
+ borderGradient.addColorStop(1, primaryColor);
82
+
83
+ ctx.strokeStyle = borderGradient;
84
+ ctx.lineWidth = 3;
85
+ ctx.beginPath();
86
+ ctx.roundRect(2, 2, width - 4, height - 4, 19);
87
+ ctx.stroke();
88
+
89
+ // Load and draw thumbnail
90
+ try {
91
+ const thumbBuffer = await cropImage({
92
+ imagePath: thumbnail,
93
+ width: 200,
94
+ height: 200,
95
+ borderRadius: 20
96
+ });
97
+ const thumbImage = await loadImage(thumbBuffer);
98
+
99
+ // Flame glow effect
100
+ ctx.shadowColor = primaryColor;
101
+ ctx.shadowBlur = 20;
102
+ ctx.fillStyle = primaryColor;
103
+ ctx.beginPath();
104
+ ctx.roundRect(30, 40, 200, 200, 20);
105
+ ctx.fill();
106
+ ctx.shadowBlur = 0;
107
+
108
+ // Draw thumbnail
109
+ ctx.drawImage(thumbImage, 30, 40, 200, 200);
110
+
111
+ // Gold border
112
+ ctx.strokeStyle = secondaryColor;
113
+ ctx.lineWidth = 3;
114
+ ctx.beginPath();
115
+ ctx.roundRect(30, 40, 200, 200, 20);
116
+ ctx.stroke();
117
+ } catch (e) {
118
+ // Placeholder
119
+ ctx.fillStyle = '#1a1a1a';
120
+ ctx.beginPath();
121
+ ctx.roundRect(30, 40, 200, 200, 20);
122
+ ctx.fill();
123
+
124
+ ctx.fillStyle = primaryColor;
125
+ ctx.font = 'bold 60px Arial';
126
+ ctx.textAlign = 'center';
127
+ ctx.textBaseline = 'middle';
128
+ ctx.fillText('🔥', 130, 140);
129
+ }
130
+
131
+ // Draw mascot if enabled
132
+ if (showMascot) {
133
+ try {
134
+ const mascotImage = await loadImage(MASCOT_PATH);
135
+ // Draw mascot in bottom right corner
136
+ ctx.drawImage(mascotImage, width - 140, height - 140, 130, 130);
137
+ } catch (e) {
138
+ // Mascot not found, continue without it
139
+ }
140
+ }
141
+
142
+ // Text content
143
+ ctx.textAlign = 'left';
144
+ ctx.textBaseline = 'top';
145
+
146
+ // Track name with flame glow
147
+ ctx.shadowColor = primaryColor;
148
+ ctx.shadowBlur = 10;
149
+ ctx.fillStyle = '#ffffff';
150
+ ctx.font = 'bold 30px Arial';
151
+ ctx.fillText(trackName, 280, 45);
152
+ ctx.shadowBlur = 0;
153
+
154
+ // Artist name
155
+ ctx.fillStyle = '#cccccc';
156
+ ctx.font = '20px Arial';
157
+ ctx.fillText(artistName, 280, 85);
158
+
159
+ // Requester with gold accent
160
+ if (requester) {
161
+ ctx.fillStyle = secondaryColor;
162
+ ctx.font = '16px Arial';
163
+ ctx.fillText(`Requested by ${requester}`, 280, 120);
164
+ }
165
+
166
+ // Progress bar
167
+ const progressY = 175;
168
+ const progressX = 280;
169
+ const barWidth = showMascot ? 450 : 520;
170
+ const actualProgressWidth = (safeProgress / 100) * barWidth;
171
+
172
+ // Background
173
+ ctx.fillStyle = 'rgba(255,255,255,0.12)';
174
+ ctx.beginPath();
175
+ ctx.roundRect(progressX, progressY, barWidth, 14, 7);
176
+ ctx.fill();
177
+
178
+ // Flame gradient fill
179
+ if (safeProgress > 0) {
180
+ const fillGradient = ctx.createLinearGradient(progressX, 0, progressX + actualProgressWidth, 0);
181
+ fillGradient.addColorStop(0, primaryColor);
182
+ fillGradient.addColorStop(1, secondaryColor);
183
+
184
+ ctx.fillStyle = fillGradient;
185
+ ctx.beginPath();
186
+ ctx.roundRect(progressX, progressY, actualProgressWidth, 14, 7);
187
+ ctx.fill();
188
+
189
+ // Glowing knob
190
+ ctx.shadowColor = secondaryColor;
191
+ ctx.shadowBlur = 10;
192
+ ctx.fillStyle = '#ffffff';
193
+ ctx.beginPath();
194
+ ctx.arc(progressX + actualProgressWidth, progressY + 7, 9, 0, Math.PI * 2);
195
+ ctx.fill();
196
+ ctx.shadowBlur = 0;
197
+ }
198
+
199
+ // Timestamps
200
+ ctx.fillStyle = '#888888';
201
+ ctx.font = '14px Arial';
202
+ ctx.textAlign = 'left';
203
+ ctx.fillText(startTime, progressX, progressY + 22);
204
+ ctx.textAlign = 'right';
205
+ ctx.fillText(endTime, progressX + barWidth, progressY + 22);
206
+
207
+ // Branding
208
+ ctx.fillStyle = '#444444';
209
+ ctx.font = '12px Arial';
210
+ ctx.textAlign = 'right';
211
+ ctx.fillText('Powered by Fuelcard', width - 20, height - 15);
212
+
213
+ // Crop final image
214
+ const finalImage = await cropImage({
215
+ imagePath: canvas.toBuffer('image/png'),
216
+ width: width,
217
+ height: height,
218
+ borderRadius: 30
219
+ });
220
+
221
+ return finalImage;
222
+ };
223
+
224
+ export default Flame;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Fuego Theme ║
4
+ * ║ Orange/Gold theme with mascot (like reference cards) ║
5
+ * ╚═══════════════════════════════════════════════════════════════╝
6
+ */
7
+
8
+ import { createCanvas, loadImage } from '@napi-rs/canvas';
9
+ import { cropImage } from 'cropify';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ const MASCOT_PATH = path.join(__dirname, '../../assets/mascot.png');
17
+
18
+ /**
19
+ * Generate a Fuego-styled music card (orange/gold like reference)
20
+ */
21
+ export const Fuego = async ({
22
+ thumbnail,
23
+ trackName = 'Unknown Track',
24
+ artistName = 'Unknown Artist',
25
+ progress = 0,
26
+ startTime = '0:00',
27
+ endTime = '0:00'
28
+ }) => {
29
+ const width = 600;
30
+ const height = 170;
31
+
32
+ // Truncate text
33
+ if (trackName.length > 22) trackName = trackName.substring(0, 19) + '...';
34
+ if (artistName.length > 28) artistName = artistName.substring(0, 25) + '...';
35
+
36
+ const safeProgress = Math.min(Math.max(progress, 0), 100);
37
+
38
+ const canvas = createCanvas(width, height);
39
+ const ctx = canvas.getContext('2d');
40
+
41
+ // Solid orange/gold background
42
+ ctx.fillStyle = '#e67e22';
43
+ ctx.beginPath();
44
+ ctx.roundRect(0, 0, width, height, 15);
45
+ ctx.fill();
46
+
47
+ // Subtle gradient overlay
48
+ const gradient = ctx.createLinearGradient(0, 0, width, 0);
49
+ gradient.addColorStop(0, 'rgba(0,0,0,0.1)');
50
+ gradient.addColorStop(0.6, 'rgba(0,0,0,0)');
51
+ gradient.addColorStop(1, 'rgba(0,0,0,0.2)');
52
+ ctx.fillStyle = gradient;
53
+ ctx.beginPath();
54
+ ctx.roundRect(0, 0, width, height, 15);
55
+ ctx.fill();
56
+
57
+ // Load thumbnail
58
+ const thumbSize = 100;
59
+ const thumbX = 20;
60
+ const thumbY = (height - thumbSize) / 2;
61
+
62
+ try {
63
+ const thumbBuffer = await cropImage({
64
+ imagePath: thumbnail,
65
+ width: thumbSize,
66
+ height: thumbSize,
67
+ borderRadius: 12
68
+ });
69
+ const thumbImage = await loadImage(thumbBuffer);
70
+
71
+ // Shadow behind thumbnail
72
+ ctx.shadowColor = 'rgba(0,0,0,0.3)';
73
+ ctx.shadowBlur = 10;
74
+ ctx.shadowOffsetX = 3;
75
+ ctx.shadowOffsetY = 3;
76
+ ctx.drawImage(thumbImage, thumbX, thumbY, thumbSize, thumbSize);
77
+ ctx.shadowBlur = 0;
78
+ ctx.shadowOffsetX = 0;
79
+ ctx.shadowOffsetY = 0;
80
+ } catch (e) {
81
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
82
+ ctx.beginPath();
83
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 12);
84
+ ctx.fill();
85
+
86
+ ctx.fillStyle = '#ffffff';
87
+ ctx.font = 'bold 40px Arial';
88
+ ctx.textAlign = 'center';
89
+ ctx.textBaseline = 'middle';
90
+ ctx.fillText('♪', thumbX + thumbSize / 2, thumbY + thumbSize / 2);
91
+ }
92
+
93
+ // Text content
94
+ const textX = thumbX + thumbSize + 20;
95
+ ctx.textAlign = 'left';
96
+ ctx.textBaseline = 'top';
97
+
98
+ // Track name (white, bold)
99
+ ctx.fillStyle = '#ffffff';
100
+ ctx.font = 'bold 22px Arial';
101
+ ctx.fillText(trackName, textX, 25);
102
+
103
+ // Artist name (darker)
104
+ ctx.fillStyle = 'rgba(0,0,0,0.6)';
105
+ ctx.font = '16px Arial';
106
+ ctx.fillText(artistName, textX, 55);
107
+
108
+ // Progress bar
109
+ const progressX = textX;
110
+ const progressY = 95;
111
+ const progressWidth = 200;
112
+ const progressHeight = 8;
113
+ const actualProgress = (safeProgress / 100) * progressWidth;
114
+
115
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
116
+ ctx.beginPath();
117
+ ctx.roundRect(progressX, progressY, progressWidth, progressHeight, 4);
118
+ ctx.fill();
119
+
120
+ if (safeProgress > 0) {
121
+ ctx.fillStyle = '#ffffff';
122
+ ctx.beginPath();
123
+ ctx.roundRect(progressX, progressY, actualProgress, progressHeight, 4);
124
+ ctx.fill();
125
+ }
126
+
127
+ // Timestamps
128
+ ctx.fillStyle = 'rgba(0,0,0,0.5)';
129
+ ctx.font = '12px Arial';
130
+ ctx.fillText(startTime, progressX, progressY + 14);
131
+ ctx.textAlign = 'right';
132
+ ctx.fillText(endTime, progressX + progressWidth, progressY + 14);
133
+
134
+ // Draw mascot on the right (extending beyond)
135
+ try {
136
+ const mascotImage = await loadImage(MASCOT_PATH);
137
+ const mascotHeight = 190;
138
+ const mascotWidth = 190;
139
+ const mascotX = width - mascotWidth + 40;
140
+ const mascotY = height - mascotHeight + 15;
141
+
142
+ ctx.drawImage(mascotImage, mascotX, mascotY, mascotWidth, mascotHeight);
143
+ } catch (e) { }
144
+
145
+ const finalImage = await cropImage({
146
+ imagePath: canvas.toBuffer('image/png'),
147
+ width: width,
148
+ height: height,
149
+ borderRadius: 20
150
+ });
151
+
152
+ return finalImage;
153
+ };
154
+
155
+ export default Fuego;
@@ -0,0 +1,212 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Fuelex Theme ║
4
+ * ║ Premium theme with mascot character featured prominently ║
5
+ * ╚═══════════════════════════════════════════════════════════════╝
6
+ */
7
+
8
+ import { createCanvas, loadImage } from '@napi-rs/canvas';
9
+ import { cropImage } from 'cropify';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+
16
+ // Path to mascot image
17
+ const MASCOT_PATH = path.join(__dirname, '../../assets/mascot.png');
18
+
19
+ /**
20
+ * @typedef {Object} FuelexOptions
21
+ * @property {string|Buffer} thumbnail - Album art URL or buffer
22
+ * @property {string} [trackName='Unknown Track'] - Song title
23
+ * @property {string} [artistName='Unknown Artist'] - Artist name
24
+ * @property {number} [progress=0] - Progress percentage (0-100)
25
+ * @property {string} [startTime='0:00'] - Current timestamp
26
+ * @property {string} [endTime='0:00'] - Total duration
27
+ * @property {string} [backgroundColor='#1a1a2e'] - Card background color
28
+ * @property {string} [accentColor='#ff6b00'] - Accent color
29
+ */
30
+
31
+ /**
32
+ * Generate a Fuelex-styled music card with the mascot character
33
+ * @param {FuelexOptions} options
34
+ * @returns {Promise<Buffer>}
35
+ */
36
+ export const Fuelex = async ({
37
+ thumbnail,
38
+ trackName = 'Unknown Track',
39
+ artistName = 'Unknown Artist',
40
+ progress = 0,
41
+ startTime = '0:00',
42
+ endTime = '0:00',
43
+ backgroundColor = '#1a1a2e',
44
+ accentColor = '#ff6b00'
45
+ }) => {
46
+ // Card dimensions (similar to reference)
47
+ const width = 900;
48
+ const height = 200;
49
+
50
+ // Truncate text
51
+ if (trackName.length > 28) trackName = trackName.substring(0, 25) + '...';
52
+ if (artistName.length > 35) artistName = artistName.substring(0, 32) + '...';
53
+
54
+ const safeProgress = Math.min(Math.max(progress, 0), 100);
55
+
56
+ // Create canvas
57
+ const canvas = createCanvas(width, height);
58
+ const ctx = canvas.getContext('2d');
59
+
60
+ // Background gradient (dark with subtle color)
61
+ const bgGradient = ctx.createLinearGradient(0, 0, width, 0);
62
+ bgGradient.addColorStop(0, backgroundColor);
63
+ bgGradient.addColorStop(0.7, '#0d0d1a');
64
+ bgGradient.addColorStop(1, '#0a0a12');
65
+
66
+ ctx.fillStyle = bgGradient;
67
+ ctx.beginPath();
68
+ ctx.roundRect(0, 0, width, height, 15);
69
+ ctx.fill();
70
+
71
+ // Accent border
72
+ const borderGradient = ctx.createLinearGradient(0, 0, width, 0);
73
+ borderGradient.addColorStop(0, accentColor);
74
+ borderGradient.addColorStop(0.5, '#ffd700');
75
+ borderGradient.addColorStop(1, accentColor);
76
+
77
+ ctx.strokeStyle = borderGradient;
78
+ ctx.lineWidth = 3;
79
+ ctx.beginPath();
80
+ ctx.roundRect(2, 2, width - 4, height - 4, 14);
81
+ ctx.stroke();
82
+
83
+ // Load and draw thumbnail on left side
84
+ const thumbSize = 140;
85
+ const thumbX = 25;
86
+ const thumbY = (height - thumbSize) / 2;
87
+
88
+ try {
89
+ const thumbBuffer = await cropImage({
90
+ imagePath: thumbnail,
91
+ width: thumbSize,
92
+ height: thumbSize,
93
+ borderRadius: 15
94
+ });
95
+ const thumbImage = await loadImage(thumbBuffer);
96
+
97
+ // Glow effect behind thumbnail
98
+ ctx.shadowColor = accentColor;
99
+ ctx.shadowBlur = 15;
100
+ ctx.drawImage(thumbImage, thumbX, thumbY, thumbSize, thumbSize);
101
+ ctx.shadowBlur = 0;
102
+
103
+ // Border on thumbnail
104
+ ctx.strokeStyle = accentColor;
105
+ ctx.lineWidth = 2;
106
+ ctx.beginPath();
107
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 15);
108
+ ctx.stroke();
109
+ } catch (e) {
110
+ // Placeholder
111
+ ctx.fillStyle = '#2a2a4e';
112
+ ctx.beginPath();
113
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 15);
114
+ ctx.fill();
115
+
116
+ ctx.fillStyle = accentColor;
117
+ ctx.font = 'bold 50px Arial';
118
+ ctx.textAlign = 'center';
119
+ ctx.textBaseline = 'middle';
120
+ ctx.fillText('♪', thumbX + thumbSize / 2, thumbY + thumbSize / 2);
121
+ }
122
+
123
+ // Draw mascot on the right side (large and prominent)
124
+ try {
125
+ const mascotImage = await loadImage(MASCOT_PATH);
126
+ const mascotWidth = 220;
127
+ const mascotHeight = 220;
128
+ const mascotX = width - mascotWidth + 15;
129
+ const mascotY = height - mascotHeight + 20;
130
+
131
+ ctx.drawImage(mascotImage, mascotX, mascotY, mascotWidth, mascotHeight);
132
+ } catch (e) {
133
+ // Mascot not available
134
+ }
135
+
136
+ // Text content (middle area)
137
+ const textX = thumbX + thumbSize + 25;
138
+ const textMaxWidth = 380;
139
+
140
+ ctx.textAlign = 'left';
141
+ ctx.textBaseline = 'top';
142
+
143
+ // Track name with glow
144
+ ctx.shadowColor = accentColor;
145
+ ctx.shadowBlur = 8;
146
+ ctx.fillStyle = '#ffffff';
147
+ ctx.font = 'bold 28px Arial';
148
+ ctx.fillText(trackName, textX, 30, textMaxWidth);
149
+ ctx.shadowBlur = 0;
150
+
151
+ // Artist name
152
+ ctx.fillStyle = '#cccccc';
153
+ ctx.font = '18px Arial';
154
+ ctx.fillText(artistName, textX, 68, textMaxWidth);
155
+
156
+ // Progress bar
157
+ const progressX = textX;
158
+ const progressY = 115;
159
+ const progressWidth = 360;
160
+ const progressHeight = 10;
161
+ const actualProgressWidth = (safeProgress / 100) * progressWidth;
162
+
163
+ // Background bar
164
+ ctx.fillStyle = 'rgba(255,255,255,0.15)';
165
+ ctx.beginPath();
166
+ ctx.roundRect(progressX, progressY, progressWidth, progressHeight, 5);
167
+ ctx.fill();
168
+
169
+ // Progress fill with gradient
170
+ if (safeProgress > 0) {
171
+ const progressGradient = ctx.createLinearGradient(progressX, 0, progressX + actualProgressWidth, 0);
172
+ progressGradient.addColorStop(0, accentColor);
173
+ progressGradient.addColorStop(1, '#ffd700');
174
+
175
+ ctx.fillStyle = progressGradient;
176
+ ctx.beginPath();
177
+ ctx.roundRect(progressX, progressY, actualProgressWidth, progressHeight, 5);
178
+ ctx.fill();
179
+
180
+ // Knob
181
+ ctx.fillStyle = '#ffffff';
182
+ ctx.beginPath();
183
+ ctx.arc(progressX + actualProgressWidth, progressY + progressHeight / 2, 7, 0, Math.PI * 2);
184
+ ctx.fill();
185
+ }
186
+
187
+ // Timestamps
188
+ ctx.fillStyle = '#888888';
189
+ ctx.font = '14px Arial';
190
+ ctx.textAlign = 'left';
191
+ ctx.fillText(startTime, progressX, progressY + 18);
192
+ ctx.textAlign = 'right';
193
+ ctx.fillText(endTime, progressX + progressWidth, progressY + 18);
194
+
195
+ // Fuelex branding
196
+ ctx.fillStyle = '#555555';
197
+ ctx.font = 'bold 11px Arial';
198
+ ctx.textAlign = 'left';
199
+ ctx.fillText('FUELCARD', textX, height - 22);
200
+
201
+ // Crop with rounded corners
202
+ const finalImage = await cropImage({
203
+ imagePath: canvas.toBuffer('image/png'),
204
+ width: width,
205
+ height: height,
206
+ borderRadius: 20
207
+ });
208
+
209
+ return finalImage;
210
+ };
211
+
212
+ export default Fuelex;