Files
tcg_proxy_maker/index.html
2026-04-11 02:52:37 +01:00

454 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MTG Proxy Maker</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
min-height: 100vh;
background: #0e0b07;
font-family: 'Palatino Linotype', Palatino, 'Book Antiqua', serif;
color: #e8dfc8;
}
header {
background: linear-gradient(90deg,#1a0f00,#2e1a00,#1a0f00);
border-bottom: 2px solid #5a3a10;
padding: 16px 24px;
display: flex;
align-items: center;
gap: 14px;
}
header .logo { font-size: 28px; }
header h1 { font-size: 20px; font-weight: 700; color: #d4af37; letter-spacing: 1px; }
header p { font-size: 12px; color: #8a7060; }
#printBtn {
margin-left: auto;
background: linear-gradient(135deg,#8b6010,#d4af37,#8b6010);
color: #1a0f00;
border: none;
border-radius: 8px;
padding: 9px 22px;
font-weight: 700;
font-size: 14px;
cursor: pointer;
font-family: inherit;
box-shadow: 0 2px 8px #d4af3744;
}
.main {
display: flex;
flex-wrap: wrap;
gap: 32px;
padding: 24px;
}
.editor {
flex: 1 1 360px;
max-width: 480px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tabs {
display: flex;
gap: 4px;
border-bottom: 1px solid #3a3020;
padding-bottom: 4px;
}
.tab {
background: transparent;
border: 1px solid transparent;
color: #8a7060;
border-radius: 6px;
padding: 5px 14px;
cursor: pointer;
font-size: 13px;
font-family: inherit;
text-transform: capitalize;
}
.tab.active { background: #3a2a10; border-color: #d4af37; color: #d4af37; font-weight: 700; }
.tab-panel { display: none; flex-direction: column; gap: 12px; }
.tab-panel.active { display: flex; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: 11px; font-weight: 600; color: #8a7a6a; letter-spacing: 0.5px; text-transform: uppercase; }
input, textarea, select {
background: #1e1a14; border: 1px solid #3a3020; border-radius: 6px;
color: #e8dfc8; padding: 7px 10px; font-size: 13px; font-family: inherit;
outline: none; width: 100%;
}
input:focus, textarea:focus, select:focus { border-color: #d4af37; box-shadow: 0 0 0 2px #d4af3733; }
select option { background: #1e1a14; color: #e8dfc8; }
textarea { resize: vertical; }
.row { display: flex; gap: 10px; }
.row .field { flex: 1; }
.color-btns { display: flex; gap: 6px; flex-wrap: wrap; }
.color-btn {
width: 36px; height: 36px; border-radius: 50%; border: 2px solid transparent;
cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center;
}
.color-btn.active { border: 3px solid #d4af37 !important; box-shadow: 0 0 8px #d4af37; }
.upload-btn {
background: #2a1e0e; border: 2px dashed #5a3a10; border-radius: 6px;
color: #e8dfc8; padding: 20px; font-size: 14px; font-family: inherit;
cursor: pointer; width: 100%; text-align: center;
}
.url-row { display: flex; gap: 6px; }
.url-row input { flex: 1; }
.set-btn {
background: #3a2a10; border: 1px solid #d4af37; color: #d4af37;
border-radius: 6px; padding: 0 12px; cursor: pointer; font-family: inherit; font-size: 13px;
}
#artPreview { display: none; flex-direction: column; gap: 6px; }
#artPreview img { width: 100%; border-radius: 8px; border: 1px solid #3a3020; max-height: 180px; object-fit: cover; }
.remove-btn {
background: transparent; border: 1px solid #5a3020; color: #c06040;
border-radius: 6px; padding: 4px 12px; cursor: pointer; font-size: 12px; font-family: inherit; width: 100%;
}
.reset-btn {
background: #2a1e0e; border: 1px solid #5a3a10; color: #c8a060;
border-radius: 6px; padding: 7px 16px; cursor: pointer; font-family: inherit; font-size: 13px;
}
.divider { border-top: 1px solid #3a3020; padding-top: 12px; }
.divider-label { font-size: 12px; color: #8a7060; margin-bottom: 8px; }
.no-stats { font-size: 13px; color: #8a7060; font-style: italic; padding: 12px 0; }
.or-divider { color: #8a7060; font-size: 12px; text-align: center; }
.preview-col { flex: 1 1 300px; display: flex; flex-direction: column; align-items: center; gap: 20px; }
.preview-label { font-size: 12px; color: #8a7060; letter-spacing: 1px; text-transform: uppercase; }
.preview-hint { font-size: 11px; color: #5a4a30; text-align: center; max-width: 280px; line-height: 1.6; }
/* Card styles */
#card {
width: 280px; min-height: 390px; border-radius: 16px; border: 4px solid #c8b87a;
background: #f9f6d5; box-shadow: 0 0 0 2px #111, 0 8px 32px #0008;
display: flex; flex-direction: column; overflow: hidden; position: relative;
color: #333;
}
#cardHeader {
display: flex; justify-content: space-between; align-items: center;
padding: 7px 10px 4px; border-bottom: 2px solid #c8b87a; background: #c8b87acc;
}
#cardName { font-weight: 700; font-size: 15px; letter-spacing: 0.3px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#cardMana { font-size: 13px; font-family: monospace; margin-left: 8px; white-space: nowrap; letter-spacing: 1px; }
#artFrame {
margin: 6px 8px; height: 140px; border-radius: 8px; border: 2px solid #c8b87a;
overflow: hidden; display: flex; align-items: center; justify-content: center;
position: relative; background: linear-gradient(135deg,#c8b87a44,#c8b87a99);
}
#artFrame img { width: 100%; height: 100%; object-fit: cover; }
#artPlaceholder { opacity: 0.4; font-size: 48px; }
#setRarity { position: absolute; bottom: 4px; right: 6px; font-size: 11px; font-weight: 700; text-shadow: 0 1px 3px #000a; }
#typeLine {
display: flex; justify-content: space-between; align-items: center;
padding: 3px 10px; font-size: 12px; font-style: italic;
border-top: 1px solid #c8b87a66; border-bottom: 1px solid #c8b87a66; background: #c8b87a99;
}
#textBox {
flex: 1; margin: 5px 8px; background: #f5f0e044; border-radius: 6px;
border: 1px solid #c8b87a55; padding: 7px 9px; min-height: 80px;
display: flex; flex-direction: column; gap: 6px;
}
#rulesDisplay { font-size: 12px; line-height: 1.45; white-space: pre-wrap; }
#flavorDisplay {
font-size: 11px; font-style: italic; opacity: 0.75;
border-top: 1px solid #c8b87a44; padding-top: 5px; line-height: 1.35; display: none;
}
#cardFooter {
display: flex; justify-content: space-between; align-items: center;
padding: 4px 10px 6px; font-size: 10px; opacity: 0.7; border-top: 1px solid #c8b87a44;
}
#ptBox {
background: #c8b87a; color: #fff; border-radius: 4px; padding: 1px 7px;
font-weight: 700; font-size: 13px; box-shadow: 0 1px 4px #0006; display: none;
}
#loyaltyBox {
background: #1a5ab8; color: #fff; border-radius: 50%; width: 26px; height: 26px;
display: none; align-items: center; justify-content: center;
font-weight: 700; font-size: 14px; box-shadow: 0 1px 4px #0006;
}
@media print {
body * { visibility: hidden; }
#card, #card * { visibility: visible; }
#card { position: fixed; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(2.5); }
}
</style>
</head>
<body>
<header>
<div class="logo">🃏</div>
<div>
<h1>MTG Proxy Maker</h1>
<p>Design · Print · Play</p>
</div>
<button id="printBtn" onclick="window.print()">🖨 Print</button>
</header>
<div class="main">
<div class="editor">
<div class="tabs">
<button class="tab active" onclick="switchTab('basic',this)">Basic</button>
<button class="tab" onclick="switchTab('art',this)">Art</button>
<button class="tab" onclick="switchTab('stats',this)">Stats</button>
</div>
<div class="tab-panel active" id="tab-basic">
<div class="field"><label>Card Name</label><input id="f-name" value="Blazing Champion" oninput="update()"></div>
<div class="field"><label>Mana Cost</label><input id="f-mana" value="{2}{R}{R}" oninput="update()"></div>
<div class="row">
<div class="field"><label>Type</label>
<select id="f-type" onchange="update()">
<option>Creature</option><option>Instant</option><option>Sorcery</option>
<option>Enchantment</option><option>Artifact</option><option>Land</option>
<option>Planeswalker</option><option>Battle</option>
</select>
</div>
<div class="field"><label>Subtype</label><input id="f-subtype" value="Human Warrior" oninput="update()"></div>
</div>
<div class="field">
<label>Color</label>
<div class="color-btns">
<button class="color-btn" data-color="W" style="background:#f9f6d5" onclick="setColor('W')" title="White"></button>
<button class="color-btn" data-color="U" style="background:#c6d9e8" onclick="setColor('U')" title="Blue">💧</button>
<button class="color-btn" data-color="B" style="background:#1a1a1a;color:#d0c8e0" onclick="setColor('B')" title="Black">💀</button>
<button class="color-btn active" data-color="R" style="background:#e87040" onclick="setColor('R')" title="Red">🔥</button>
<button class="color-btn" data-color="G" style="background:#3a7a48;color:#d4f5d0" onclick="setColor('G')" title="Green">🌲</button>
<button class="color-btn" data-color="C" style="background:#c0bdb5" onclick="setColor('C')" title="Colorless"></button>
<button class="color-btn" data-color="M" style="background:linear-gradient(135deg,gold,blue,black)" onclick="setColor('M')" title="Multi"></button>
</div>
</div>
<div class="field"><label>Rarity</label>
<select id="f-rarity" onchange="update()">
<option>Common</option><option>Uncommon</option><option selected>Rare</option><option>Mythic Rare</option>
</select>
</div>
<div class="field"><label>Rules Text</label>
<textarea id="f-rules" rows="4" oninput="update()">Haste
When Blazing Champion enters the battlefield, it deals 2 damage to any target.</textarea>
</div>
<div class="field"><label>Flavor Text</label>
<textarea id="f-flavor" rows="2" oninput="update()" style="font-style:italic">"No retreat. No mercy. Only fire."</textarea>
</div>
<div class="field"><label>Artist</label><input id="f-artist" value="Proxy Artist" oninput="update()"></div>
</div>
<div class="tab-panel" id="tab-art">
<div class="field">
<label>Upload Card Art</label>
<input type="file" id="fileInput" accept="image/*" style="display:none" onchange="handleFile(event)">
<button class="upload-btn" onclick="document.getElementById('fileInput').click()">📁 Click to upload image</button>
</div>
<div class="or-divider">— or paste an image URL —</div>
<div class="field">
<label>Image URL</label>
<div class="url-row">
<input id="urlInput" placeholder="https://...">
<button class="set-btn" onclick="setImageUrl()">Set</button>
</div>
</div>
<div id="artPreview">
<img id="artThumb" src="" alt="preview">
<button class="remove-btn" onclick="removeImage()">Remove Image</button>
</div>
<div class="field"><label>Set Symbol</label><input id="f-set" value="★" oninput="update()" maxlength="4"></div>
</div>
<div class="tab-panel" id="tab-stats">
<div id="creatureStats">
<div class="row">
<div class="field"><label>Power</label><input id="f-power" value="4" oninput="update()"></div>
<div class="field"><label>Toughness</label><input id="f-toughness" value="3" oninput="update()"></div>
</div>
</div>
<div id="pwStats" style="display:none">
<div class="field"><label>Starting Loyalty</label><input id="f-loyalty" value="4" oninput="update()"></div>
</div>
<div id="noStats" class="no-stats" style="display:none">No stats for this card type.</div>
<div class="divider">
<div class="divider-label">Quick Reset</div>
<button class="reset-btn" onclick="resetCard()">↩ Reset to Default</button>
</div>
</div>
</div>
<div class="preview-col">
<div class="preview-label">Preview</div>
<div id="card">
<div id="cardHeader">
<div id="cardName">Blazing Champion</div>
<div id="cardMana">{2}{R}{R}</div>
</div>
<div id="artFrame">
<div id="artPlaceholder">🔥</div>
<img id="cardArt" src="" alt="art" style="display:none;width:100%;height:100%;object-fit:cover">
<div id="setRarity" style="color:#d4af37">★ R</div>
</div>
<div id="typeLine">
<span id="typeDisplay">Creature — Human Warrior</span>
<span id="setDisplay"></span>
</div>
<div id="textBox">
<div id="rulesDisplay"></div>
<div id="flavorDisplay"></div>
</div>
<div id="cardFooter">
<span id="artistDisplay">✦ Proxy Artist</span>
<div id="ptBox">4/3</div>
<div id="loyaltyBox" style="display:none">4</div>
</div>
</div>
<div class="preview-hint">Use the Print button to print on card stock.<br>Cut to 2.5" × 3.5" for standard size.</div>
</div>
</div>
<script>
const COLORS = {
W: { bg:'#f9f6d5', border:'#c8b87a', text:'#333', symbol:'☀' },
U: { bg:'#c6d9e8', border:'#3a6b9c', text:'#fff', symbol:'💧' },
B: { bg:'#1a1a1a', border:'#5a4a6e', text:'#d0c8e0', symbol:'💀' },
R: { bg:'#e87040', border:'#8b2500', text:'#fff', symbol:'🔥' },
G: { bg:'#3a7a48', border:'#1a4025', text:'#d4f5d0', symbol:'🌲' },
C: { bg:'#c0bdb5', border:'#7a7068', text:'#333', symbol:'◇' },
M: { bg:'linear-gradient(160deg,#f9f6d5,#c6d9e8,#1a1a1a,#e87040,#3a7a48)', border:'#b8960c', text:'#fff', symbol:'★' },
};
const RARITY_COLORS = { Common:'#aaa', Uncommon:'#a0c0d0', Rare:'#d4af37', 'Mythic Rare':'#e07020' };
let currentColor = 'R';
let currentImageUrl = '';
function setColor(c) {
currentColor = c;
document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active'));
document.querySelector('[data-color="'+c+'"]').classList.add('active');
update();
}
function update() {
const col = COLORS[currentColor];
const type = document.getElementById('f-type').value;
const rarity = document.getElementById('f-rarity').value;
const sym = document.getElementById('f-set').value || '★';
const card = document.getElementById('card');
card.style.borderColor = col.border;
card.style.background = col.bg;
card.style.color = col.text;
card.style.boxShadow = '0 0 0 2px #111, 0 0 30px '+col.border+'55, 0 8px 32px #0008';
const hdr = document.getElementById('cardHeader');
hdr.style.background = col.border+'cc';
hdr.style.borderBottomColor = col.border;
document.getElementById('cardName').textContent = document.getElementById('f-name').value || 'Card Name';
document.getElementById('cardMana').textContent = document.getElementById('f-mana').value;
const af = document.getElementById('artFrame');
af.style.borderColor = col.border;
af.style.background = currentImageUrl ? 'transparent' : 'linear-gradient(135deg,'+col.border+'44,'+col.border+'99)';
document.getElementById('artPlaceholder').textContent = col.symbol;
const rc = RARITY_COLORS[rarity]||'#aaa';
document.getElementById('setRarity').style.color = rc;
document.getElementById('setRarity').textContent = sym+' '+(rarity?rarity[0]:'C');
const tl = document.getElementById('typeLine');
tl.style.background = col.border+'99';
const sub = document.getElementById('f-subtype').value;
document.getElementById('typeDisplay').textContent = [type,sub].filter(Boolean).join(' — ');
document.getElementById('setDisplay').textContent = sym;
document.getElementById('textBox').style.borderColor = col.border+'55';
document.getElementById('rulesDisplay').textContent = document.getElementById('f-rules').value;
const flavor = document.getElementById('f-flavor').value;
const fd = document.getElementById('flavorDisplay');
if (flavor) { fd.textContent = flavor; fd.style.display='block'; } else { fd.style.display='none'; }
document.getElementById('cardFooter').style.borderTopColor = col.border+'44';
document.getElementById('artistDisplay').textContent = '✦ '+(document.getElementById('f-artist').value||'Unknown');
const ptBox = document.getElementById('ptBox');
const loyBox = document.getElementById('loyaltyBox');
ptBox.style.background = col.border;
const isCreature = type==='Creature'||type==='Battle';
const isPW = type==='Planeswalker';
if (isCreature) {
ptBox.style.display='inline-block';
ptBox.textContent = document.getElementById('f-power').value+'/'+document.getElementById('f-toughness').value;
loyBox.style.display='none';
} else if (isPW) {
loyBox.style.display='flex';
loyBox.textContent = document.getElementById('f-loyalty').value;
ptBox.style.display='none';
} else {
ptBox.style.display='none';
loyBox.style.display='none';
}
document.getElementById('creatureStats').style.display = isCreature?'block':'none';
document.getElementById('pwStats').style.display = isPW?'block':'none';
document.getElementById('noStats').style.display = (!isCreature&&!isPW)?'block':'none';
}
function switchTab(name, btn) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.tab-panel').forEach(p => {
p.classList.toggle('active', p.id==='tab-'+name);
});
}
function handleFile(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => applyImage(ev.target.result);
reader.readAsDataURL(file);
}
function setImageUrl() {
const url = document.getElementById('urlInput').value.trim();
if (url) applyImage(url);
}
function applyImage(url) {
currentImageUrl = url;
const img = document.getElementById('cardArt');
img.src = url; img.style.display='block';
document.getElementById('artPlaceholder').style.display='none';
document.getElementById('artThumb').src = url;
document.getElementById('artPreview').style.display='flex';
update();
}
function removeImage() {
currentImageUrl = '';
const img = document.getElementById('cardArt');
img.src=''; img.style.display='none';
document.getElementById('artPlaceholder').style.display='block';
document.getElementById('artPreview').style.display='none';
document.getElementById('urlInput').value='';
document.getElementById('fileInput').value='';
update();
}
function resetCard() {
document.getElementById('f-name').value='Blazing Champion';
document.getElementById('f-mana').value='{2}{R}{R}';
document.getElementById('f-type').value='Creature';
document.getElementById('f-subtype').value='Human Warrior';
document.getElementById('f-rarity').value='Rare';
document.getElementById('f-rules').value='Haste\nWhen Blazing Champion enters the battlefield, it deals 2 damage to any target.';
document.getElementById('f-flavor').value='"No retreat. No mercy. Only fire."';
document.getElementById('f-artist').value='Proxy Artist';
document.getElementById('f-power').value='4';
document.getElementById('f-toughness').value='3';
document.getElementById('f-loyalty').value='4';
document.getElementById('f-set').value='★';
setColor('R');
removeImage();
}
update();
</script>
</body>
</html>