<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Tile Map Editor</title>
<style>
body { margin: 0; font-family: sans-serif; background: #121212; color: #eee; }
.topbar {
display: flex; justify-content: space-around; align-items: center;
background: #1f1f1f; padding: 0.5rem; position: sticky; top: 0; z-index: 100;
}
.btn { font-size: 1.5rem; background: transparent; border: none; color: #eee;
padding: 0.5rem; border-radius: 12px; cursor: pointer; }
.btn.active, .btn:hover { background: #333; }
.section { display: none; padding: 1rem; }
.section.active { display: block; }
.scroll-x { display: flex; overflow-x: auto; gap: 0.5rem; padding: 0.5rem 0; }
.folder, .image {
flex: 0 0 auto; width: 80px; height: 80px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
text-align: center; font-size: 0.8rem; cursor: pointer;
}
.folder { background: #2a2a4a; color: #ccc; }
.image { background: #333; overflow: hidden; border: 2px solid transparent; }
.image img { width: 100%; height: 100%; object-fit: cover; border-radius: 12px; }
.image.selected { border-color: #6cf; }
.breadcrumb { margin-bottom: 0.5rem; }
.breadcrumb span { cursor: pointer; color: #6cf; margin-right: 0.25rem; }
.breadcrumb span::after { content: ' /'; }
.breadcrumb span:last-child::after { content: ''; }
#preview { margin-top: 1rem; text-align: center; }
#preview img { max-width: 90%; max-height: 200px; border-radius: 12px; border: 2px solid #6cf; background: #000; cursor: pointer; }
/* Overlay */
#overlay {
display: none; position: fixed; inset: 0;
background: rgba(0,0,0,0.95); color: #fff; z-index: 200;
padding: 1rem; overflow: hidden;
}
#overlayContent { margin-top: 2.5rem; text-align: center; height: calc(100% - 3rem); display: flex; flex-direction: column; }
#overlayClose {
position: absolute; top: 0.5rem; right: 0.5rem;
font-size: 1.5rem; background: #333; border: none; border-radius: 8px;
color: #eee; cursor: pointer; padding: 0.25rem 0.5rem;
}
/* Tabs */
#groupTabs {
display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 0.5rem;
}
#groupTabs button {
background: #333; border: none; color: #eee;
padding: 0.25rem 0.75rem; border-radius: 6px; cursor: pointer;
}
#groupTabs button.active { background: #6cf; color: #000; }
#pickedImages {
display: flex; gap: 0.5rem; margin-bottom: 0.5rem;
flex-wrap: wrap; justify-content: center;
}
.pickedTile { position: relative; display: inline-block; }
.pickedTile canvas {
border: 2px solid #6cf; border-radius: 8px; background: #000;
}
.pickedTile span {
position: absolute; bottom: -14px; left: 50%; transform: translateX(-50%);
font-size: 10px; color: #fff; background: rgba(0,0,0,0.6);
padding: 1px 4px; border-radius: 4px;
}
.removeBtn {
position: absolute; top: -8px; right: -8px;
background: #f33; color: #fff; border: none; border-radius: 50%;
font-size: 0.8rem; cursor: pointer; width: 18px; height: 18px;
line-height: 16px; padding: 0;
}
#tileViewport {
flex: 1; overflow: auto; border: 2px solid #444; background: #000;
}
#tileContainer { position: relative; display: inline-block; }
#tileContainer img {
display: block; border-radius: 12px; border: 2px solid #6cf;
}
.grid-cell {
position: absolute;
border: 1px solid rgba(255,255,255,0.3);
box-sizing: border-box;
cursor: pointer;
font-size: 10px;
color: #fff;
}
.grid-cell span {
position: absolute; top: 2px; left: 2px;
background: rgba(0,0,0,0.5);
padding: 0 2px;
border-radius: 3px;
font-size: 9px;
}
.grid-cell:hover { background: rgba(255,255,255,0.1); }
</style>
</head>
<body>
<div class="topbar">
<button class="btn" data-target="files">📂</button>
<button class="btn" data-overlay="tiles">🧩</button>
<button class="btn" data-overlay="map">🗺️</button>
<button class="btn" data-overlay="game">🎮</button>
</div>
<div id="files" class="section">
<h2>Files 📂</h2>
<div class="breadcrumb"></div>
<div class="scroll-x" id="fileList"></div>
<div id="preview"></div>
</div>
<!-- Shared Overlay -->
<div id="overlay">
<button id="overlayClose">✖️</button>
<div id="overlayContent"></div>
</div>
<script>
let selectedImage = null;
let selectedImageName = null;
let selectedImageDiv = null;
let selectedTileSize = null;
// Groups
let groups = [{ id: 0, url: null, tiles: [] }];
let currentGroup = 0;
let nextUniqueId = 1; // start at 1 (0 = "no object")
// -------- BUTTON HANDLING --------
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', () => {
if (btn.dataset.target) {
document.querySelectorAll('.btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.section').forEach(sec => sec.classList.remove('active'));
btn.classList.add('active');
document.getElementById(btn.dataset.target).classList.add('active');
if (btn.dataset.target === 'files') loadFiles('');
} else if (btn.dataset.overlay) {
openOverlay(btn.dataset.overlay);
}
});
});
document.querySelectorAll('.btn')[0].click();
// -------- FILE BROWSER --------
const fileList = document.getElementById('fileList');
const breadcrumb = document.querySelector('.breadcrumb');
const preview = document.getElementById('preview');
function loadFiles(sub) {
fetch('media.php?sub=' + encodeURIComponent(sub))
.then(r => r.json())
.then(data => {
renderBreadcrumb(data.breadcrumb);
renderFiles(data);
})
.catch(err => console.error(err));
}
function renderBreadcrumb(crumbs) {
breadcrumb.innerHTML = '';
crumbs.forEach(c => {
const span = document.createElement('span');
span.textContent = c.label;
span.onclick = () => loadFiles(c.sub);
breadcrumb.appendChild(span);
});
}
function renderFiles(data) {
fileList.innerHTML = '';
preview.innerHTML = '';
// Folders
data.folders.forEach(f => {
const div = document.createElement('div');
div.className = 'folder';
div.textContent = f.name;
div.onclick = () => {
if (/^\d+$/.test(f.name)) {
selectedTileSize = parseInt(f.name, 10);
loadFiles(f.sub);
} else {
loadFiles(f.sub);
}
};
fileList.appendChild(div);
});
// Images
data.images.forEach(img => {
const div = document.createElement('div');
div.className = 'image';
const im = document.createElement('img');
im.src = img.url;
im.alt = img.name;
div.appendChild(im);
div.onclick = () => {
if (selectedImageDiv) selectedImageDiv.classList.remove('selected');
div.classList.add('selected');
selectedImageDiv = div;
selectedImage = img.url;
selectedImageName = img.name;
showPreview(img);
};
fileList.appendChild(div);
});
}
function showPreview(img) {
preview.innerHTML = `
<h3>Selected: ${img.name}</h3>
<img id="previewImg" src="${img.url}" alt="${img.name}">
`;
// Click big preview → tile picker
document.getElementById('previewImg').onclick = () => openOverlay('tiles');
}
// -------- OVERLAY --------
const overlay = document.getElementById('overlay');
const overlayContent = document.getElementById('overlayContent');
document.getElementById('overlayClose').onclick = () => { overlay.style.display = 'none'; };
function openOverlay(type) {
overlayContent.innerHTML = '';
overlay.style.display = 'block';
if (type === 'tiles') {
if (selectedImage && selectedTileSize) {
overlayContent.innerHTML = `
<h2>Tile Picker 🧩</h2>
<p>Tile size: ${selectedTileSize}</p>
<div id="groupTabs"></div>
<div id="pickedImages"></div>
<div id="tileViewport">
<div id="tileContainer">
<img id="tileImage" src="${selectedImage}" alt="${selectedImageName}">
</div>
</div>
`;
renderTabs();
renderPicked();
const imgEl = document.getElementById('tileImage');
imgEl.onload = () => {
const container = document.getElementById('tileContainer');
const w = imgEl.naturalWidth;
const h = imgEl.naturalHeight;
imgEl.style.width = w + "px";
imgEl.style.height = h + "px";
container.style.width = w + "px";
container.style.height = h + "px";
container.querySelectorAll('.grid-cell').forEach(c => c.remove());
const cols = Math.floor(w / selectedTileSize);
for (let y = 0; y < h; y += selectedTileSize) {
for (let x = 0; x < w; x += selectedTileSize) {
const cell = document.createElement('div');
cell.className = 'grid-cell';
cell.style.left = x + 'px';
cell.style.top = y + 'px';
cell.style.width = selectedTileSize + 'px';
cell.style.height = selectedTileSize + 'px';
const row = Math.floor(y / selectedTileSize);
const col = Math.floor(x / selectedTileSize);
const tileIndex = row * cols + col + 1;
const label = document.createElement('span');
label.textContent = tileIndex;
cell.appendChild(label);
cell.onclick = () => pickTile(imgEl, x, y, selectedTileSize, selectedImage);
container.appendChild(cell);
}
}
};
} else {
overlayContent.innerHTML = `<h2>Tile Picker 🧩</h2><p>Select an image and a numeric folder first.</p>`;
}
}
}
// -------- TABS + GROUPS --------
function renderTabs() {
const tabBar = document.getElementById('groupTabs');
tabBar.innerHTML = '';
groups.forEach((g, idx) => {
const btn = document.createElement('button');
btn.textContent = "Group " + (idx+1);
if (idx === currentGroup) btn.classList.add('active');
btn.onclick = () => { currentGroup = idx; renderTabs(); renderPicked(); };
tabBar.appendChild(btn);
});
const addBtn = document.createElement('button');
addBtn.textContent = "+";
addBtn.onclick = () => {
groups.push({ id: groups.length, url: null, tiles: [] });
currentGroup = groups.length - 1;
renderTabs();
renderPicked();
};
tabBar.appendChild(addBtn);
}
function renderPicked() {
const container = document.getElementById('pickedImages');
container.innerHTML = '';
const group = groups[currentGroup];
group.tiles.forEach((tile, idx) => {
const wrapper = document.createElement('div');
wrapper.className = 'pickedTile';
const canvas = document.createElement('canvas');
canvas.width = tile.size;
canvas.height = tile.size;
const ctx = canvas.getContext('2d');
ctx.putImageData(tile.data, 0, 0);
const removeBtn = document.createElement('button');
removeBtn.className = 'removeBtn';
removeBtn.textContent = "×";
removeBtn.onclick = () => {
group.tiles.splice(idx, 1);
renderPicked();
};
const idLabel = document.createElement('span');
idLabel.textContent = `ID ${tile.uniqueId}`;
wrapper.appendChild(canvas);
wrapper.appendChild(removeBtn);
wrapper.appendChild(idLabel);
container.appendChild(wrapper);
});
}
// -------- PICK TILE --------
function pickTile(imgEl, x, y, size, url) {
const group = groups[currentGroup];
if (group.url && group.url !== url) {
alert("This group already uses a different image. Make a new group.");
return;
}
group.url = url;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgEl, x, y, size, size, 0, 0, size, size);
const data = ctx.getImageData(0, 0, size, size);
group.tiles.push({ size, data, uniqueId: nextUniqueId++ });
renderPicked();
}
</script>
</body>
</html>