Compare commits

...

9 commits

6 changed files with 82 additions and 37 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

BIN
web/static/v1_common.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
web/static/v1_epic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
web/static/v1_rare.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
web/static/v1_uncommon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -43,9 +43,17 @@
<input type="text" id="packInput" placeholder="Card Name" style="width: 100%; margin-bottom: 0.5rem;"><br> <input type="text" id="packInput" placeholder="Card Name" style="width: 100%; margin-bottom: 0.5rem;"><br>
<input type="number" id="powerInput" placeholder="⚡ Power" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br> <input type="number" id="powerInput" placeholder="⚡ Power" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br>
<input type="number" id="charmInput" placeholder="❤️ Charm" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br> <input type="number" id="charmInput" placeholder="❤️ Charm" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br>
<input type="number" id="popularityInput" placeholder="💫 Popularity" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br> <input type="number" id="witInput" placeholder="💫 Wit" min="0" max="999" style="width: 100%; margin-bottom: 0.5rem;"><br>
<textarea id="flavorInput" placeholder="Flavor text..." rows="4" style="width: 100%; margin-bottom: 0.5rem;"></textarea><br> <textarea id="flavorInput" placeholder="Flavor text..." rows="4" style="width: 100%; margin-bottom: 0.5rem;"></textarea><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;">
<option value="v1_common.png" selected>Common</option>
<option value="v1_uncommon.png">Uncommon</option>
<option value="v1_rare.png">Rare</option>
<option value="v1_epic.png">Epic</option>
<option value="v1_legendary.png">Legendary</option>
</select>
<button id="downloadBtn" onclick="generateCard()">✨ Create Card</button> <button id="downloadBtn" onclick="generateCard()">✨ Create Card</button>
</div> </div>
<!-- Right: SVG Card Output --> <!-- Right: SVG Card Output -->
@ -64,15 +72,20 @@ const nameInput = document.getElementById("nameInput");
const packInput = document.getElementById("packInput"); const packInput = document.getElementById("packInput");
const powerInput = document.getElementById("powerInput"); const powerInput = document.getElementById("powerInput");
const charmInput = document.getElementById("charmInput"); const charmInput = document.getElementById("charmInput");
const popularityInput = document.getElementById("popularityInput"); const witInput = document.getElementById("witInput");
const flavorInput = document.getElementById("flavorInput"); const flavorInput = document.getElementById("flavorInput");
const artistInput = document.getElementById("artistInput");
const downloadBtn = document.getElementById("downloadBtn"); const downloadBtn = document.getElementById("downloadBtn");
const uploadArt = document.getElementById("uploadArt");
const frameSelect = document.getElementById("frameSelect");
let frameImage = new Image();
let uploadedImage = null;
// 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(packName, cardName) { async function generateCardId(...args) {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const data = encoder.encode(packName + ':' + cardName); const data = encoder.encode(args.join(':'));
const hashBuffer = await crypto.subtle.digest('SHA-1', data); const hashBuffer = await crypto.subtle.digest('SHA-1', data);
// Convert hash to hex // Convert hash to hex
@ -83,18 +96,40 @@ async function generateCardId(packName, cardName) {
return hex.slice(0, 12); return hex.slice(0, 12);
} }
// Load the card frame image
const frameImage = new Image();
frameImage.src = "{{ url_for('static', filename='card-frame.png') }}"; // <-- replace with your transparent card frame PNG
frameImage.onload = () => { uploadArt.addEventListener("change", function(e) {
drawCard(); const file = e.target.files[0];
}; if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
uploadedImage = new Image();
uploadedImage.onload = drawCard;
uploadedImage.src = event.target.result;
};
reader.readAsDataURL(file);
});
// Update frame image when selection changes
function updateFrame() {
frameImage.src = "{{ url_for('static', filename='') }}" + frameSelect.value;
frameImage.onload = drawCard;
}
// Initial frame load
updateFrame();
// Change frame when dropdown changes
frameSelect.addEventListener("change", updateFrame);
function drawCard() { function drawCard() {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#f5f5f5";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw uploaded art as background if present
if (uploadedImage) {
ctx.drawImage(uploadedImage, 0, 0, canvas.width, canvas.height);
}
ctx.drawImage(frameImage, 0, 0, canvas.width, canvas.height); ctx.drawImage(frameImage, 0, 0, canvas.width, canvas.height);
// Name & pack // Name & pack
@ -103,60 +138,70 @@ function drawCard() {
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText(nameInput.value, canvas.width / 2, 100); ctx.fillText(nameInput.value, canvas.width / 2, 100);
ctx.font = "20px sans-serif"; ctx.font = "20px sans-serif";
ctx.fillText(packInput.value, canvas.width / 2, 140); ctx.fillText(packInput.value, canvas.width / 2, 160);
ctx.textAlign = "left"; ctx.textAlign = "left";
// Stats // Stats
ctx.font = "30px sans-serif"; ctx.font = "30px sans-serif";
ctx.fillText("Power: " + powerInput.value, 60, 1050); ctx.fillText(powerInput.value, 220, 823);
ctx.fillText("Charm: " + charmInput.value, 300, 1050); ctx.fillText(charmInput.value, 373, 823);
ctx.fillText("Popularity: " + popularityInput.value, 540, 1050); ctx.fillText(witInput.value, 512, 823);
// Flavor // Flavor
ctx.font = "italic 24px serif"; ctx.font = "italic 24px serif";
wrapText(flavorInput.value, 60, 950, 680, 30); ctx.textAlign = "center";
wrapText(flavorInput.value, canvas.width / 2, 965, 558, 30);
// Artist
ctx.font = "30px sans-serif";
ctx.textAlign = "center";
ctx.fillStyle = "#000000";
ctx.fillText(artistInput.value, canvas.width / 2, 1060);
// Defer ID drawing to a separate async step! // Defer ID drawing to a separate async step!
drawCardId(); drawCardId();
} }
async function drawCardId() { async function drawCardId() {
const id = await generateCardId(packInput.value, nameInput.value); const id = await generateCardId(packInput.value, nameInput.value, powerInput.value, charmInput.value, witInput.value, artistInput.value);
ctx.font = "20px sans-serif"; ctx.font = "15px sans-serif";
ctx.fillText("KC-" + id, canvas.width - 350, canvas.height - 60); ctx.fillText("KC-" + id, canvas.width/2, canvas.height - 30);
} }
function wrapText(text, x, y, maxWidth, lineHeight) { function wrapText(text, x, y, maxWidth, lineHeight) {
const words = text.split(" "); const lines = text.split('\n');
let line = ""; for (let i = 0; i < lines.length; i++) {
for (let n = 0; n < words.length; n++) { let words = lines[i].split(" ");
const testLine = line + words[n] + " "; let line = "";
const metrics = ctx.measureText(testLine); for (let n = 0; n < words.length; n++) {
const testWidth = metrics.width; const testLine = line + words[n] + " ";
if (testWidth > maxWidth && n > 0) { const metrics = ctx.measureText(testLine);
ctx.fillText(line, x, y); const testWidth = metrics.width;
line = words[n] + " "; if (testWidth > maxWidth && n > 0) {
y += lineHeight; ctx.fillText(line, x, y);
} else { line = words[n] + " ";
line = testLine; y += lineHeight;
} else {
line = testLine;
}
} }
ctx.fillText(line, x, y);
y += lineHeight;
} }
ctx.fillText(line, x, y);
} }
// Update card live when inputs change // Update card live when inputs change
[nameInput, powerInput, charmInput, popularityInput, flavorInput,packInput].forEach(input => { [nameInput, powerInput, charmInput, witInput, flavorInput,packInput,artistInput].forEach(input => {
input.addEventListener("input", drawCard); input.addEventListener("input", drawCard);
}); });
// Download button // Download button
downloadBtn.addEventListener("click", () => { downloadBtn.addEventListener("click", () => {
const link = document.createElement("a"); const link = document.createElement("a");
link.download = "kemoverse-card.png"; link.download = "kemoverse-card.webp";
link.href = canvas.toDataURL("image/png"); link.href = canvas.toDataURL("image/webp", 0.95);
link.click(); link.click();
}); });
</script> </script>
{% endblock %} {% endblock %}