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.
- package/15.png +0 -0
- package/README.md +125 -0
- package/assets/banner-base.svg +76 -0
- package/assets/banner.png +0 -0
- package/assets/mascot.png +0 -0
- package/file_0000000041d871f8a662e051a98493c7.png +0 -0
- package/generate-banner.js +145 -0
- package/package.json +31 -0
- package/src/index.js +25 -0
- package/src/themes/Aqua.js +156 -0
- package/src/themes/Flame.js +224 -0
- package/src/themes/Fuego.js +155 -0
- package/src/themes/Fuelex.js +212 -0
- package/src/themes/Oscuro.js +157 -0
- package/src/themes/Rosa.js +156 -0
- package/test.js +129 -0
|
@@ -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);
|