Compare commits
4 commits
8f1dda579f
...
98c36f913f
Author | SHA1 | Date | |
---|---|---|---|
98c36f913f | |||
21005d311c | |||
7ae5c8f62a | |||
01df661b60 |
1 changed files with 69 additions and 8 deletions
|
@ -41,8 +41,10 @@
|
||||||
<p>Use this tool to create custom Kemoverse cards! Fill in the details below, upload your art, and generate a card image.</p>
|
<p>Use this tool to create custom Kemoverse cards! Fill in the details below, upload your art, and generate a card image.</p>
|
||||||
<p>Note: The card ID is generated based on the details, so make sure to fill them out!</p>
|
<p>Note: The card ID is generated based on the details, so make sure to fill them out!</p>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 0.5rem;">
|
||||||
|
<input type="checkbox" id="restrictStatsToggle" checked>
|
||||||
|
<label for="restrictStatsToggle">Restrict stats by rarity</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="text" id="nameInput" placeholder="Card Name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
<input type="text" id="nameInput" placeholder="Card Name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
||||||
<input type="text" id="packInput" placeholder="Pack name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
<input type="text" id="packInput" placeholder="Pack name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
||||||
|
@ -53,11 +55,11 @@
|
||||||
<input type="text" id="artistInput" placeholder="Artist Name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
<input type="text" id="artistInput" placeholder="Artist Name" style="width: 100%; margin-bottom: 0.5rem;"><br>
|
||||||
<input type="file" id="uploadArt" style="margin-bottom: 0.5rem;"><br>
|
<input type="file" id="uploadArt" style="margin-bottom: 0.5rem;"><br>
|
||||||
<select id="frameSelect" style="width: 100%; margin-bottom: 0.5rem;">
|
<select id="frameSelect" style="width: 100%; margin-bottom: 0.5rem;">
|
||||||
<option value="v1_common.png" selected>Common</option>
|
<option value="v1_common.png" selected>Common (100)</option>
|
||||||
<option value="v1_uncommon.png">Uncommon</option>
|
<option value="v1_uncommon.png">Uncommon (140)</option>
|
||||||
<option value="v1_rare.png">Rare</option>
|
<option value="v1_rare.png">Rare (180)</option>
|
||||||
<option value="v1_epic.png">Epic</option>
|
<option value="v1_epic.png">Epic (220)</option>
|
||||||
<option value="v1_legendary.png">Legendary</option>
|
<option value="v1_legendary.png">Legendary (250)</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="downloadBtn" onclick="generateCard()">✨ Create Card</button>
|
<button id="downloadBtn" onclick="generateCard()">✨ Create Card</button>
|
||||||
<p>All the processing is done in your browser, no data is sent to the server.</p>
|
<p>All the processing is done in your browser, no data is sent to the server.</p>
|
||||||
|
@ -86,9 +88,19 @@ const artistInput = document.getElementById("artistInput");
|
||||||
const downloadBtn = document.getElementById("downloadBtn");
|
const downloadBtn = document.getElementById("downloadBtn");
|
||||||
const uploadArt = document.getElementById("uploadArt");
|
const uploadArt = document.getElementById("uploadArt");
|
||||||
const frameSelect = document.getElementById("frameSelect");
|
const frameSelect = document.getElementById("frameSelect");
|
||||||
|
const restrictStatsToggle = document.getElementById("restrictStatsToggle");
|
||||||
let frameImage = new Image();
|
let frameImage = new Image();
|
||||||
let uploadedImage = null;
|
let uploadedImage = null;
|
||||||
|
|
||||||
|
// Rarity caps for stats
|
||||||
|
const rarityCaps = {
|
||||||
|
"v1_common.png": 100,
|
||||||
|
"v1_uncommon.png": 140,
|
||||||
|
"v1_rare.png": 180,
|
||||||
|
"v1_epic.png": 220,
|
||||||
|
"v1_legendary.png": 250
|
||||||
|
};
|
||||||
|
|
||||||
// Generate a unique ID for the card based on pack and card name
|
// Generate a unique ID for the card based on pack and card name
|
||||||
async function generateCardId(...args) {
|
async function generateCardId(...args) {
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
@ -130,6 +142,10 @@ frameSelect.addEventListener("change", updateFrame);
|
||||||
|
|
||||||
function drawCard() {
|
function drawCard() {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
// Place a white background
|
||||||
|
ctx.fillStyle = "#FFFFFF"; // white background
|
||||||
|
// make it smaller than the size of the canvas
|
||||||
|
ctx.fillRect(30, 30, canvas.width - 60, canvas.height - 60);
|
||||||
|
|
||||||
// Draw uploaded art as cropped and centered background if present
|
// Draw uploaded art as cropped and centered background if present
|
||||||
if (uploadedImage) {
|
if (uploadedImage) {
|
||||||
|
@ -226,6 +242,50 @@ function wrapText(text, x, y, maxWidth, lineHeight) {
|
||||||
input.addEventListener("input", drawCard);
|
input.addEventListener("input", drawCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enforce stat caps based on rarity and frame
|
||||||
|
function enforceStatCap(changedInput) {
|
||||||
|
if (!restrictStatsToggle.checked) return;
|
||||||
|
const cap = rarityCaps[frameSelect.value] || 9999;
|
||||||
|
let power = parseInt(powerInput.value) || 0;
|
||||||
|
let charm = parseInt(charmInput.value) || 0;
|
||||||
|
let wit = parseInt(witInput.value) || 0;
|
||||||
|
let total = power + charm + wit;
|
||||||
|
if (total > cap) {
|
||||||
|
// Reduce the changed input to fit the cap
|
||||||
|
const excess = total - cap;
|
||||||
|
if (changedInput === powerInput) {
|
||||||
|
power = Math.max(0, power - excess);
|
||||||
|
powerInput.value = power;
|
||||||
|
} else if (changedInput === charmInput) {
|
||||||
|
charm = Math.max(0, charm - excess);
|
||||||
|
charmInput.value = charm;
|
||||||
|
} else if (changedInput === witInput) {
|
||||||
|
wit = Math.max(0, wit - excess);
|
||||||
|
witInput.value = wit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[powerInput, charmInput, witInput].forEach(input => {
|
||||||
|
input.addEventListener("input", function() {
|
||||||
|
enforceStatCap(this);
|
||||||
|
drawCard();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
frameSelect.addEventListener("change", function() {
|
||||||
|
if (restrictStatsToggle.checked) {
|
||||||
|
enforceStatCap();
|
||||||
|
}
|
||||||
|
updateFrame();
|
||||||
|
});
|
||||||
|
|
||||||
|
restrictStatsToggle.addEventListener("change", function() {
|
||||||
|
if (this.checked) {
|
||||||
|
enforceStatCap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Download button
|
// Download button
|
||||||
downloadBtn.addEventListener("click", () => {
|
downloadBtn.addEventListener("click", () => {
|
||||||
// Sanitize file name
|
// Sanitize file name
|
||||||
|
@ -287,6 +347,7 @@ zipBtn.onclick = async () => {
|
||||||
const safePack = (packInput.value || "pack").replace(/[^a-z0-9_\-]/gi, "_");
|
const safePack = (packInput.value || "pack").replace(/[^a-z0-9_\-]/gi, "_");
|
||||||
const imgFileName = `kemoverse_${safePack}_${safeName}.webp`;
|
const imgFileName = `kemoverse_${safePack}_${safeName}.webp`;
|
||||||
const jsonFileName = `kemoverse_${safePack}_${safeName}.json`;
|
const jsonFileName = `kemoverse_${safePack}_${safeName}.json`;
|
||||||
|
const zipFileName = `kemoverse_${safePack}_${safeName}.zip`;
|
||||||
|
|
||||||
// Create ZIP
|
// Create ZIP
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
@ -300,7 +361,7 @@ zipBtn.onclick = async () => {
|
||||||
const content = await zip.generateAsync({type: "blob"});
|
const content = await zip.generateAsync({type: "blob"});
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = URL.createObjectURL(content);
|
link.href = URL.createObjectURL(content);
|
||||||
link.download = `kemoverse_${safePack}_${safeName}.zip`;
|
link.download = zipFileName;
|
||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue