PAU
can you build something for this app so i can upload my own sprits to games i make
BUDDY
Awesome idea, Pau! 🎮 I built you a Sprite Uploader & Manager — you can upload your own sprite images, preview them, name them, and even see them animated on a mini canvas! Let me know what you'd like to add next!
CODE
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>🎮 Sprite Uploader</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #eee; min-height: 100vh; padding: 20px; } h1 { text-align: center; font-size: 2rem; margin-bottom: 6px; color: #f0c040; text-shadow: 0 0 10px #f0c04080; } .subtitle { text-align: center; font-size: 0.95rem; color: #aaa; margin-bottom: 24px; } .main-layout { display: flex; gap: 20px; flex-wrap: wrap; max-width: 1000px; margin: 0 auto; } /* --- Upload Panel --- */ .upload-panel { background: #16213e; border-radius: 16px; padding: 20px; flex: 1 1 280px; border: 2px dashed #f0c04060; display: flex; flex-direction: column; align-items: center; gap: 14px; } .drop-zone { width: 100%; background: #0f3460; border-radius: 12px; padding: 30px 20px; text-align: center; cursor: pointer; border: 2px dashed #f0c040; transition: background 0.2s; } .drop-zone:hover { background: #1a4a80; } .drop-zone .icon { font-size: 3rem; } .drop-zone p { margin-top: 8px; font-size: 0.9rem; color: #ccc; } #fileInput { display: none; } .name-row { width: 100%; display: flex; gap: 8px; } .name-row input { flex: 1; padding: 8px 12px; border-radius: 8px; border: none; background: #0f3460; color: #fff; font-size: 0.9rem; } .name-row input::placeholder { color: #888; } .btn { padding: 9px 18px; border-radius: 8px; border: none; font-size: 0.9rem; font-weight: bold; cursor: pointer; transition: transform 0.1s, opacity 0.2s; } .btn:hover { opacity: 0.85; transform: scale(1.04); } .btn:active { transform: scale(0.97); } .btn-upload { background: #f0c040; color: #1a1a2e; } .btn-danger { background: #e05050; color: #fff; font-size: 0.75rem; padding: 5px 10px; } .btn-copy { background: #50c8a0; color: #1a1a2e; font-size: 0.75rem; padding: 5px 10px; } /* --- Sprite Grid --- */ .sprites-panel { flex: 2 1 400px; display: flex; flex-direction: column; gap: 14px; } .sprites-panel h2 { font-size: 1.1rem; color: #f0c040; } .sprite-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 14px; } .sprite-card { background: #16213e; border-radius: 12px; padding: 10px; display: flex; flex-direction: column; align-items: center; gap: 8px; border: 2px solid transparent; transition: border-color 0.2s; cursor: pointer; } .sprite-card:hover { border-color: #f0c040; } .sprite-card.selected { border-color: #50c8a0; background: #0f3460; } .sprite-card img { width: 80px; height: 80px; object-fit: contain; image-rendering: pixelated; border-radius: 6px; background: #0a0a1a; } .sprite-name { font-size: 0.78rem; color: #ddd; text-align: center; word-break: break-all; } .card-btns { display: flex; gap: 5px; } .empty-msg { color: #666; font-size: 0.9rem; text-align: center; padding: 30px; } /* --- Preview Panel --- */ .preview-panel { background: #16213e; border-radius: 16px; padding: 20px; max-width: 1000px; margin: 20px auto 0; border: 2px solid #f0c04040; } .preview-panel h2 { color: #f0c040; margin-bottom: 12px; font-size: 1.1rem; } .preview-controls { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; } .preview-controls label { font-size: 0.85rem; color: #ccc; } .preview-controls input[type=range] { accent-color: #f0c040; } #previewCanvas { background: #0a0a1a; border-radius: 10px; display: block; border: 2px solid #0f3460; image-rendering: pixelated; } .code-box { margin-top: 16px; background: #0a0a1a; border-radius: 10px; padding: 14px; font-family: monospace; font-size: 0.82rem; color: #50c8a0; white-space: pre-wrap; word-break: break-all; border: 1px solid #0f3460; max-height: 110px; overflow-y: auto; } .copy-row { display: flex; justify-content: flex-end; margin-top: 6px; } /* scrollbar */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: #1a1a2e; } ::-webkit-scrollbar-thumb { background: #0f3460; border-radius: 4px; } </style> </head> <body> <h1>🎮 Sprite Uploader</h1> <p class="subtitle">Upload your sprites, preview them, and grab the code for your games!</p> <div class="main-layout"> <!-- Upload Panel --> <div class="upload-panel"> <div class="drop-zone" id="dropZone"> <div class="icon">🖼️</div> <p>Click or drag & drop<br>an image here</p> <p style="margin-top:6px; font-size:0.78rem; color:#888;">PNG, JPG, GIF, WebP</p> </div> <input type="file" id="fileInput" accept="image/*" multiple /> <div class="name-row"> <input type="text" id="spriteName" placeholder="Sprite name (optional)" maxlength="30" /> <button class="btn btn-upload" id="uploadBtn">Add Sprite</button> </div> <div id="uploadPreview" style="display:none; flex-direction:column; align-items:center; gap:6px; width:100%;"> <img id="uploadPreviewImg" style="max-width:100px; max-height:100px; border-radius:8px; background:#0a0a1a; image-rendering:pixelated;" /> <span id="uploadPreviewName" style="font-size:0.8rem; color:#aaa;"></span> </div> </div> <!-- Sprite Grid --> <div class="sprites-panel"> <h2>🗂️ My Sprites (<span id="spriteCount">0</span>)</h2> <div class="sprite-grid" id="spriteGrid"> <p class="empty-msg" id="emptyMsg">No sprites yet!<br>Upload one to get started 🚀</p> </div> </div> </div> <!-- Preview Panel --> <div class="preview-panel"> <h2>🔍 Sprite Preview</h2> <div class="preview-controls"> <label>Size: <input type="range" id="sizeSlider" min="32" max="300" value="100"> <span id="sizeVal">100</span>px</label> <label>Speed: <input type="range" id="speedSlider" min="1" max="10" value="4"> <span id="speedVal">4</span></label> <label style="display:flex;align-items:center;gap:6px;"> <input type="checkbox" id="bounceCheck" checked style="accent-color:#f0c040;"> Bounce </label> <label style="display:flex;align-items:center;gap:6px;"> <input type="checkbox" id="rotateCheck" style="accent-color:#f0c040;"> Rotate </label> </div> <canvas id="previewCanvas" width="700" height="180"></canvas> <p style="font-size:0.78rem; color:#666; margin-top:6px;">👆 Click a sprite card above to preview it here</p> <div style="margin-top:14px;"> <p style="font-size:0.85rem; color:#aaa; margin-bottom:6px;">📋 Copy this code to use your sprite in a game:</p> <div class="code-box" id="codeBox">// Select a sprite to see its code here</div> <div class="copy-row"> <button class="btn btn-copy" id="copyBtn">Copy Code</button> </div> </div> </div> <script> const sprites = []; let selectedIndex = -1; let pendingDataURL = null; let animFrame = null; let spriteX = 50, spriteY = 80, velX = 3, velY = 1.5, angle = 0; const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('fileInput'); const uploadBtn = document.getElementById('uploadBtn'); const spriteGrid = document.getElementById('spriteGrid'); const emptyMsg = document.getElementById('emptyMsg'); const spriteCount = document.getElementById('spriteCount'); const uploadPreview = document.getElementById('uploadPreview'); const uploadPreviewImg = document.getElementById('uploadPreviewImg'); const uploadPreviewName= document.getElementById('uploadPreviewName'); const spriteName = document.getElementById('spriteName'); const canvas = document.getElementById('previewCanvas'); const ctx = canvas.getContext('2d'); const sizeSlider = document.getElementById('sizeSlider'); const speedSlider = document.getElementById('speedSlider'); const sizeVal = document.getElementById('sizeVal'); const speedVal = document.getElementById('speedVal'); const bounceCheck = document.getElementById('bounceCheck'); const rotateCheck = document.getElementById('rotateCheck'); const codeBox = document.getElementById('codeBox'); const copyBtn = document.getElementById('copyBtn'); // Resize canvas responsively function resizeCanvas() { const w = Math.min(700, canvas.parentElement.clientWidth - 40); canvas.width = w; canvas.height = 180; } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // --- File picking --- dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.style.background = '#1a4a80'; }); dropZone.addEventListener('dragleave', () => { dropZone.style.background = ''; }); dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.style.background = ''; const files = e.dataTransfer.files; if (files.length) loadFile(files[0]); }); fileInput.addEventListener('change', () => { if (fileInput.files.length) loadFile(fileInput.files[0]); fileInput.value = ''; }); function loadFile(file) { if (!file.type.startsWith('image/')) { alert('Please choose an image file!'); return; } const reader = new FileReader(); reader.onload = e => { pendingDataURL = e.target.result; uploadPreviewImg.src = pendingDataURL; const defaultName = file.name.replace(/\.[^.]+$/, ''); if (!spriteName.value) spriteName.value = defaultName; uploadPreviewName.textContent = spriteName.value || defaultName; uploadPreview.style.display = 'flex'; }; reader.readAsDataURL(file); } // --- Add sprite --- uploadBtn.addEventListener('click', addSprite); function addSprite() { if (!pendingDataURL) { alert('Pick an image first!'); return; } const name = spriteName.value.trim() || `Sprite ${sprites.length + 1}`; const img = new Image(); img.src = pendingDataURL; sprites.push({ name, dataURL: pendingDataURL, img }); pendingDataURL = null; spriteName.value = ''; uploadPreview.style.display = 'none'; renderGrid(); selectSprite(sprites.length - 1); } // --- Grid --- function renderGrid() { spriteGrid.innerHTML = ''; spriteCount.textContent = sprites.length; if (sprites.length === 0) { spriteGrid.innerHTML = '<p class="empty-msg" id="emptyMsg">No sprites yet!<br>Upload one to get started 🚀</p>'; return; } sprites.forEach((s, i) => { const card = document.createElement('div'); card.className = 'sprite-card' + (i === selectedIndex ? ' selected' : ''); card.innerHTML = ` <img src="${s.dataURL}" alt="${s.name}" title="${s.name}" /> <div class="sprite-name">${s.name}</div> <div class="card-btns"> <button class="btn btn-danger" data-del="${i}">🗑️</button> </div>`; card.addEventListener('click', e => { if (e.target.dataset.del !== undefined) return; selectSprite(i); }); card.querySelector('[data-del]').addEventListener('click', e => { e.stopPropagation(); deleteSprite(i); }); spriteGrid.appendChild(card); }); } function deleteSprite(i) { sprites.splice(i, 1); if (selectedIndex >= sprites.length) selectedIndex = sprites.length - 1; renderGrid(); if (selectedIndex >= 0) selectSprite(selectedIndex); else stopAnimation(); } // --- Select & Preview --- function selectSprite(i) { selectedIndex = i; renderGrid(); startAnimation(sprites[i]); updateCodeBox(sprites[i]); } sizeSlider.addEventListener('input', () => { sizeVal.textContent = sizeSlider.value; }); speedSlider.addEventListener('input', () => { speedVal.textContent = speedSlider.value; }); function startAnimation(sprite) { if (animFrame) cancelAnimationFrame(animFrame); spriteX = 50; spriteY = 80; velX = 3; velY = 1.5; angle = 0; function draw() { const size = parseInt(sizeSlider.value); const speed = parseInt(speedSlider.value) * 0.5; const bounce = bounceCheck.checked; const rotate = rotateCheck.checked; ctx.clearRect(0, 0, canvas.width, canvas.height); // checkerboard bg const sq = 20; for (let row = 0; row < Math.ceil(canvas.height/sq); row++) { for (let col = 0; col < Math.ceil(canvas.width/sq); col++) { ctx.fillStyle = (row + col) % 2 === 0 ? '#111122' : '#0d0d1e'; ctx.fillRect(col*sq, row*sq, sq, sq); } } if (bounce) { spriteX += velX * speed; spriteY += velY * speed; if (spriteX + size > canvas.width) { spriteX = canvas.width - size; velX = -Math.abs(velX); } if (spriteX < 0) { spriteX = 0; velX = Math.abs(velX); } if (spriteY + size > canvas.height) { spriteY = canvas.height - size; velY = -Math.abs(velY); } if (spriteY < 0) { spriteY = 0; velY = Math.abs(velY); } } else { spriteX = (canvas.width - size) / 2; spriteY = (canvas.height - size) / 2; } ctx.save(); if (rotate) { angle += 0.02 * speed; ctx.translate(spriteX + size/2, spriteY + size/2); ctx.rotate(angle); ctx.drawImage(sprite.img, -size/2, -size/2, size, size); } else { ctx.drawImage(sprite.img, spriteX, spriteY, size, size); } ctx.restore(); animFrame = requestAnimationFrame(draw); } draw(); } function stopAnimation() { if (animFrame) cancelAnimationFrame(animFrame); ctx.clearRect(0, 0, canvas.width, canvas.height); codeBox.textContent = '// Select a sprite to see its code here'; } // --- Code box --- function updateCodeBox(sprite) { const code = `// ── ${sprite.name} ────────────────────────── // Paste this at the top of your game's <script>: const ${safeName(sprite.name)}Img = new Image(); ${safeName(sprite.name)}Img.src = "${sprite.dataURL.substring(0,60)}..."; // (copy the full Data URL from the app) // Then in your draw() function: ctx.drawImage(${safeName(sprite.name)}Img, x, y, width, height);`; codeBox.textContent = code; } function safeName(name) { return name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&'); } copyBtn.addEventListener('click', () => { if (selectedIndex < 0) { alert('Select a sprite first!'); return; } const s = sprites[selectedIndex]; const fullCode = `// ── ${s.name} ────────────────────────── const ${safeName(s.name)}Img = new Image(); ${safeName(s.name)}Img.src = "${s.dataURL}"; // In your draw() function: ctx.drawImage(${safeName(s.name)}Img, x, y, width, height);`; navigator.clipboard.writeText(fullCode).then(() => { copyBtn.textContent = '✅ Copied!'; setTimeout(() => copyBtn.textContent = 'Copy Code', 2000); }); }); // initial idle canvas (function idleCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); const sq = 20; for (let row = 0; row < Math.ceil(canvas.height/sq); row++) { for (let col = 0; col < Math.ceil(canvas.width/sq); col++) { ctx.fillStyle = (row + col) % 2 === 0 ? '#111122' : '#0d0d1e'; ctx.fillRect(col*sq, row*sq, sq, sq); } } ctx.fillStyle = '#333'; ctx.font = '16px Segoe UI'; ctx.textAlign = 'center'; ctx.fillText('Select a sprite to preview it here ✨', canvas.width/2, canvas.height/2); })(); </script> </body> </html>
PREVIEW
PICTURES0/50
No pictures yet. Add one, then ask your buddy to put it in your app!