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,157 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Oscuro Theme ║
4
+ * ║ Dark 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 an Oscuro-styled music card (dark like reference)
20
+ */
21
+ export const Oscuro = async ({
22
+ thumbnail,
23
+ trackName = 'Unknown Track',
24
+ artistName = 'Unknown Artist',
25
+ progress = 0,
26
+ startTime = '0:00',
27
+ endTime = '0:00',
28
+ accentColor = '#ff6b00'
29
+ }) => {
30
+ const width = 600;
31
+ const height = 170;
32
+
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
+ // Dark gradient background
42
+ const bgGradient = ctx.createLinearGradient(0, 0, width, height);
43
+ bgGradient.addColorStop(0, '#1a1a2e');
44
+ bgGradient.addColorStop(1, '#0d0d1a');
45
+
46
+ ctx.fillStyle = bgGradient;
47
+ ctx.beginPath();
48
+ ctx.roundRect(0, 0, width, height, 15);
49
+ ctx.fill();
50
+
51
+ // Accent border
52
+ ctx.strokeStyle = accentColor;
53
+ ctx.lineWidth = 2;
54
+ ctx.beginPath();
55
+ ctx.roundRect(1, 1, width - 2, height - 2, 14);
56
+ ctx.stroke();
57
+
58
+ // Thumbnail
59
+ const thumbSize = 100;
60
+ const thumbX = 20;
61
+ const thumbY = (height - thumbSize) / 2;
62
+
63
+ try {
64
+ const thumbBuffer = await cropImage({
65
+ imagePath: thumbnail,
66
+ width: thumbSize,
67
+ height: thumbSize,
68
+ borderRadius: 12
69
+ });
70
+ const thumbImage = await loadImage(thumbBuffer);
71
+
72
+ ctx.shadowColor = accentColor;
73
+ ctx.shadowBlur = 10;
74
+ ctx.drawImage(thumbImage, thumbX, thumbY, thumbSize, thumbSize);
75
+ ctx.shadowBlur = 0;
76
+
77
+ // Accent border on thumbnail
78
+ ctx.strokeStyle = accentColor;
79
+ ctx.lineWidth = 2;
80
+ ctx.beginPath();
81
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 12);
82
+ ctx.stroke();
83
+ } catch (e) {
84
+ ctx.fillStyle = '#2a2a4a';
85
+ ctx.beginPath();
86
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 12);
87
+ ctx.fill();
88
+
89
+ ctx.fillStyle = accentColor;
90
+ ctx.font = 'bold 40px Arial';
91
+ ctx.textAlign = 'center';
92
+ ctx.textBaseline = 'middle';
93
+ ctx.fillText('♪', thumbX + thumbSize / 2, thumbY + thumbSize / 2);
94
+ }
95
+
96
+ // Text
97
+ const textX = thumbX + thumbSize + 20;
98
+ ctx.textAlign = 'left';
99
+ ctx.textBaseline = 'top';
100
+
101
+ ctx.fillStyle = '#ffffff';
102
+ ctx.font = 'bold 22px Arial';
103
+ ctx.fillText(trackName, textX, 25);
104
+
105
+ ctx.fillStyle = '#aaaaaa';
106
+ ctx.font = '16px Arial';
107
+ ctx.fillText(artistName, textX, 55);
108
+
109
+ // Progress bar
110
+ const progressX = textX;
111
+ const progressY = 95;
112
+ const progressWidth = 200;
113
+ const progressHeight = 8;
114
+ const actualProgress = (safeProgress / 100) * progressWidth;
115
+
116
+ ctx.fillStyle = 'rgba(255,255,255,0.15)';
117
+ ctx.beginPath();
118
+ ctx.roundRect(progressX, progressY, progressWidth, progressHeight, 4);
119
+ ctx.fill();
120
+
121
+ if (safeProgress > 0) {
122
+ ctx.fillStyle = accentColor;
123
+ ctx.beginPath();
124
+ ctx.roundRect(progressX, progressY, actualProgress, progressHeight, 4);
125
+ ctx.fill();
126
+ }
127
+
128
+ // Timestamps
129
+ ctx.fillStyle = '#888888';
130
+ ctx.font = '12px Arial';
131
+ ctx.textAlign = 'left';
132
+ ctx.fillText(startTime, progressX, progressY + 14);
133
+ ctx.textAlign = 'right';
134
+ ctx.fillText(endTime, progressX + progressWidth, progressY + 14);
135
+
136
+ // Mascot
137
+ try {
138
+ const mascotImage = await loadImage(MASCOT_PATH);
139
+ const mascotHeight = 190;
140
+ const mascotWidth = 190;
141
+ const mascotX = width - mascotWidth + 40;
142
+ const mascotY = height - mascotHeight + 15;
143
+
144
+ ctx.drawImage(mascotImage, mascotX, mascotY, mascotWidth, mascotHeight);
145
+ } catch (e) { }
146
+
147
+ const finalImage = await cropImage({
148
+ imagePath: canvas.toBuffer('image/png'),
149
+ width: width,
150
+ height: height,
151
+ borderRadius: 20
152
+ });
153
+
154
+ return finalImage;
155
+ };
156
+
157
+ export default Oscuro;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Rosa Theme ║
4
+ * ║ Pink/Magenta 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 Rosa-styled music card (pink/magenta like reference)
20
+ */
21
+ export const Rosa = 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
+ if (trackName.length > 22) trackName = trackName.substring(0, 19) + '...';
33
+ if (artistName.length > 28) artistName = artistName.substring(0, 25) + '...';
34
+
35
+ const safeProgress = Math.min(Math.max(progress, 0), 100);
36
+
37
+ const canvas = createCanvas(width, height);
38
+ const ctx = canvas.getContext('2d');
39
+
40
+ // Pink/Magenta gradient
41
+ const bgGradient = ctx.createLinearGradient(0, 0, width, height);
42
+ bgGradient.addColorStop(0, '#e91e63');
43
+ bgGradient.addColorStop(1, '#c2185b');
44
+
45
+ ctx.fillStyle = bgGradient;
46
+ ctx.beginPath();
47
+ ctx.roundRect(0, 0, width, height, 15);
48
+ ctx.fill();
49
+
50
+ // Overlay
51
+ const overlay = ctx.createLinearGradient(0, 0, width, 0);
52
+ overlay.addColorStop(0, 'rgba(0,0,0,0.05)');
53
+ overlay.addColorStop(0.6, 'rgba(0,0,0,0)');
54
+ overlay.addColorStop(1, 'rgba(0,0,0,0.15)');
55
+ ctx.fillStyle = overlay;
56
+ ctx.beginPath();
57
+ ctx.roundRect(0, 0, width, height, 15);
58
+ ctx.fill();
59
+
60
+ // Thumbnail
61
+ const thumbSize = 100;
62
+ const thumbX = 20;
63
+ const thumbY = (height - thumbSize) / 2;
64
+
65
+ try {
66
+ const thumbBuffer = await cropImage({
67
+ imagePath: thumbnail,
68
+ width: thumbSize,
69
+ height: thumbSize,
70
+ borderRadius: 12
71
+ });
72
+ const thumbImage = await loadImage(thumbBuffer);
73
+
74
+ ctx.shadowColor = 'rgba(0,0,0,0.3)';
75
+ ctx.shadowBlur = 10;
76
+ ctx.shadowOffsetX = 3;
77
+ ctx.shadowOffsetY = 3;
78
+ ctx.drawImage(thumbImage, thumbX, thumbY, thumbSize, thumbSize);
79
+ ctx.shadowBlur = 0;
80
+ ctx.shadowOffsetX = 0;
81
+ ctx.shadowOffsetY = 0;
82
+ } catch (e) {
83
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
84
+ ctx.beginPath();
85
+ ctx.roundRect(thumbX, thumbY, thumbSize, thumbSize, 12);
86
+ ctx.fill();
87
+
88
+ ctx.fillStyle = '#ffffff';
89
+ ctx.font = 'bold 40px Arial';
90
+ ctx.textAlign = 'center';
91
+ ctx.textBaseline = 'middle';
92
+ ctx.fillText('♪', thumbX + thumbSize / 2, thumbY + thumbSize / 2);
93
+ }
94
+
95
+ // Text
96
+ const textX = thumbX + thumbSize + 20;
97
+ ctx.textAlign = 'left';
98
+ ctx.textBaseline = 'top';
99
+
100
+ ctx.fillStyle = '#ffffff';
101
+ ctx.font = 'bold 22px Arial';
102
+ ctx.fillText(trackName, textX, 25);
103
+
104
+ ctx.fillStyle = 'rgba(255,255,255,0.75)';
105
+ ctx.font = '16px Arial';
106
+ ctx.fillText(artistName, textX, 55);
107
+
108
+ // Progress
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(255,255,255,0.6)';
129
+ ctx.font = '12px Arial';
130
+ ctx.textAlign = 'left';
131
+ ctx.fillText(startTime, progressX, progressY + 14);
132
+ ctx.textAlign = 'right';
133
+ ctx.fillText(endTime, progressX + progressWidth, progressY + 14);
134
+
135
+ // Mascot
136
+ try {
137
+ const mascotImage = await loadImage(MASCOT_PATH);
138
+ const mascotHeight = 190;
139
+ const mascotWidth = 190;
140
+ const mascotX = width - mascotWidth + 40;
141
+ const mascotY = height - mascotHeight + 15;
142
+
143
+ ctx.drawImage(mascotImage, mascotX, mascotY, mascotWidth, mascotHeight);
144
+ } catch (e) { }
145
+
146
+ const finalImage = await cropImage({
147
+ imagePath: canvas.toBuffer('image/png'),
148
+ width: width,
149
+ height: height,
150
+ borderRadius: 20
151
+ });
152
+
153
+ return finalImage;
154
+ };
155
+
156
+ export default Rosa;
package/test.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * ╔═══════════════════════════════════════════════════════════════╗
3
+ * ║ FUELCARD - Test Script ║
4
+ * ║ Generate all themes for testing ║
5
+ * ╚═══════════════════════════════════════════════════════════════╝
6
+ */
7
+
8
+ import { Fuego, Aqua, Rosa, Oscuro, Classic, Neon, Flame, Fuelex } from './src/index.js';
9
+ import fs from 'fs';
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 outputDir = path.join(__dirname, 'test-output');
17
+ if (!fs.existsSync(outputDir)) {
18
+ fs.mkdirSync(outputDir, { recursive: true });
19
+ }
20
+
21
+ const sampleTrack = {
22
+ thumbnail: 'https://i.scdn.co/image/ab67616d0000b2738863bc11d2aa12b54f5aeb36',
23
+ trackName: 'Blinding Lights',
24
+ artistName: 'The Weeknd',
25
+ startTime: '1:45',
26
+ endTime: '3:22',
27
+ progress: 52
28
+ };
29
+
30
+ async function generateTestCards() {
31
+ console.log('╔═══════════════════════════════════════════════════════════════╗');
32
+ console.log('║ FUELCARD TEST SUITE ║');
33
+ console.log('║ By Ramkrishna & ZayDocs ║');
34
+ console.log('╚═══════════════════════════════════════════════════════════════╝');
35
+ console.log();
36
+ console.log('=== NEW THEMES (Like Reference) ===');
37
+ console.log();
38
+
39
+ // Fuego (Orange)
40
+ console.log('[TEST] Generating Fuego theme (orange)...');
41
+ try {
42
+ const card = await Fuego(sampleTrack);
43
+ fs.writeFileSync(path.join(outputDir, 'fuego-theme.png'), card);
44
+ console.log(' ✓ Saved: fuego-theme.png');
45
+ } catch (error) {
46
+ console.error(' ✗ Error:', error.message);
47
+ }
48
+
49
+ // Aqua (Blue)
50
+ console.log('[TEST] Generating Aqua theme (blue)...');
51
+ try {
52
+ const card = await Aqua(sampleTrack);
53
+ fs.writeFileSync(path.join(outputDir, 'aqua-theme.png'), card);
54
+ console.log(' ✓ Saved: aqua-theme.png');
55
+ } catch (error) {
56
+ console.error(' ✗ Error:', error.message);
57
+ }
58
+
59
+ // Rosa (Pink)
60
+ console.log('[TEST] Generating Rosa theme (pink)...');
61
+ try {
62
+ const card = await Rosa(sampleTrack);
63
+ fs.writeFileSync(path.join(outputDir, 'rosa-theme.png'), card);
64
+ console.log(' ✓ Saved: rosa-theme.png');
65
+ } catch (error) {
66
+ console.error(' ✗ Error:', error.message);
67
+ }
68
+
69
+ // Oscuro (Dark)
70
+ console.log('[TEST] Generating Oscuro theme (dark)...');
71
+ try {
72
+ const card = await Oscuro(sampleTrack);
73
+ fs.writeFileSync(path.join(outputDir, 'oscuro-theme.png'), card);
74
+ console.log(' ✓ Saved: oscuro-theme.png');
75
+ } catch (error) {
76
+ console.error(' ✗ Error:', error.message);
77
+ }
78
+
79
+ console.log();
80
+ console.log('=== LEGACY THEMES ===');
81
+ console.log();
82
+
83
+ // Fuelex
84
+ console.log('[TEST] Generating Fuelex theme...');
85
+ try {
86
+ const card = await Fuelex({ ...sampleTrack, accentColor: '#ff6b00' });
87
+ fs.writeFileSync(path.join(outputDir, 'fuelex-theme.png'), card);
88
+ console.log(' ✓ Saved: fuelex-theme.png');
89
+ } catch (error) {
90
+ console.error(' ✗ Error:', error.message);
91
+ }
92
+
93
+ // Classic
94
+ console.log('[TEST] Generating Classic theme...');
95
+ try {
96
+ const card = await Classic(sampleTrack);
97
+ fs.writeFileSync(path.join(outputDir, 'classic-theme.png'), card);
98
+ console.log(' ✓ Saved: classic-theme.png');
99
+ } catch (error) {
100
+ console.error(' ✗ Error:', error.message);
101
+ }
102
+
103
+ // Neon
104
+ console.log('[TEST] Generating Neon theme...');
105
+ try {
106
+ const card = await Neon({ ...sampleTrack, glowColor: '#00ffff', accentColor: '#ff00ff' });
107
+ fs.writeFileSync(path.join(outputDir, 'neon-theme.png'), card);
108
+ console.log(' ✓ Saved: neon-theme.png');
109
+ } catch (error) {
110
+ console.error(' ✗ Error:', error.message);
111
+ }
112
+
113
+ // Flame
114
+ console.log('[TEST] Generating Flame theme...');
115
+ try {
116
+ const card = await Flame({ ...sampleTrack, showMascot: true });
117
+ fs.writeFileSync(path.join(outputDir, 'flame-theme.png'), card);
118
+ console.log(' ✓ Saved: flame-theme.png');
119
+ } catch (error) {
120
+ console.error(' ✗ Error:', error.message);
121
+ }
122
+
123
+ console.log();
124
+ console.log('═══════════════════════════════════════════════════════════════');
125
+ console.log('Test complete! Check the test-output folder.');
126
+ console.log('═══════════════════════════════════════════════════════════════');
127
+ }
128
+
129
+ generateTestCards().catch(console.error);