454 lines
19 KiB
HTML
454 lines
19 KiB
HTML
<!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>
|