Spaces:
Runtime error
Runtime error
Update public/index.html
Browse files- public/index.html +71 -4
public/index.html
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>Super City β Character Editor</title>
|
| 7 |
-
<link href="https://fonts.
|
|
|
|
| 8 |
<style>
|
| 9 |
:root {
|
| 10 |
--bg: #0a0c10;
|
|
@@ -28,7 +29,7 @@
|
|
| 28 |
body {
|
| 29 |
background: var(--bg);
|
| 30 |
color: var(--text);
|
| 31 |
-
font-family: '
|
| 32 |
font-size: 15px;
|
| 33 |
min-height: 100vh;
|
| 34 |
}
|
|
@@ -81,7 +82,7 @@
|
|
| 81 |
.panel-header { padding: 10px 14px; background: rgba(0,200,255,0.06); border-bottom: 1px solid var(--border); font-size: 11px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: var(--accent); display: flex; align-items: center; gap: 8px; }
|
| 82 |
.panel-header .count { margin-left: auto; font-family: 'Share Tech Mono', monospace; font-size: 12px; color: var(--text-dim); font-weight: 400; letter-spacing: 0; }
|
| 83 |
.search-wrap { padding: 10px; border-bottom: 1px solid var(--border); }
|
| 84 |
-
.search-input { width: 100%; background: var(--bg); border: 1px solid var(--border-bright); color: var(--text-bright); font-family: '
|
| 85 |
.search-input:focus { border-color: var(--accent); }
|
| 86 |
.search-input::placeholder { color: var(--text-dim); }
|
| 87 |
.char-list { max-height: 520px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--border-bright) transparent; }
|
|
@@ -130,7 +131,7 @@
|
|
| 130 |
|
| 131 |
/* ACTIONS */
|
| 132 |
.actions-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
| 133 |
-
.btn { padding: 10px 22px; border: none; cursor: pointer; font-family: '
|
| 134 |
.btn-primary { background: var(--accent); color: #000; }
|
| 135 |
.btn-primary:hover { background: #30dfff; box-shadow: 0 0 20px rgba(0,200,255,0.4); }
|
| 136 |
.btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--border-bright); }
|
|
@@ -150,6 +151,15 @@
|
|
| 150 |
.tool-badge.ok { background: rgba(61,220,151,0.1); color: var(--civilian); border: 1px solid rgba(61,220,151,0.3); }
|
| 151 |
.tool-badge.missing { background: rgba(255,68,85,0.1); color: var(--danger); border: 1px solid rgba(255,68,85,0.3); }
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
/* TOAST */
|
| 154 |
.toast { position: fixed; bottom: 30px; right: 30px; background: var(--panel); border: 1px solid var(--accent); color: var(--text-bright); padding: 12px 20px; border-radius: 4px; font-size: 14px; font-weight: 600; letter-spacing: 1px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); transform: translateY(80px); opacity: 0; transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1); z-index: 1000; }
|
| 155 |
.toast.show { transform: translateY(0); opacity: 1; }
|
|
@@ -189,6 +199,15 @@
|
|
| 189 |
<div class="upload-sub">or click to browse Β· .apk files only Β· max 200 MB</div>
|
| 190 |
<input type="file" id="apkInput" accept=".apk">
|
| 191 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
<div class="progress-wrap" id="progressWrap">
|
| 193 |
<div class="progress-label" id="progressLabel">Uploadingβ¦</div>
|
| 194 |
<div class="progress-bar-outer"><div class="progress-bar-inner" id="progressBar"></div></div>
|
|
@@ -365,6 +384,7 @@ function uploadAPK(file) {
|
|
| 365 |
<div class="file-info">π¦ <span class="file-info-name">${file.name}</span>
|
| 366 |
Β· ${characters.length} characters loaded Β· session: ${sessionToken.split('_')[0]}</div>`;
|
| 367 |
document.getElementById('editorLayout').style.display = 'grid';
|
|
|
|
| 368 |
setStep(2);
|
| 369 |
showToast(`β Loaded ${characters.length} characters`);
|
| 370 |
} else {
|
|
@@ -378,6 +398,53 @@ function uploadAPK(file) {
|
|
| 378 |
xhr.send(form);
|
| 379 |
}
|
| 380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
// ββ PARSE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 382 |
function parseCharacters(raw) {
|
| 383 |
characters = [];
|
|
|
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<title>Super City β Character Editor</title>
|
| 7 |
+
<link href="https://fonts.cdnfonts.com/css/copper-black" rel="stylesheet">
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
| 9 |
<style>
|
| 10 |
:root {
|
| 11 |
--bg: #0a0c10;
|
|
|
|
| 29 |
body {
|
| 30 |
background: var(--bg);
|
| 31 |
color: var(--text);
|
| 32 |
+
font-family: 'Copper Black', serif;
|
| 33 |
font-size: 15px;
|
| 34 |
min-height: 100vh;
|
| 35 |
}
|
|
|
|
| 82 |
.panel-header { padding: 10px 14px; background: rgba(0,200,255,0.06); border-bottom: 1px solid var(--border); font-size: 11px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: var(--accent); display: flex; align-items: center; gap: 8px; }
|
| 83 |
.panel-header .count { margin-left: auto; font-family: 'Share Tech Mono', monospace; font-size: 12px; color: var(--text-dim); font-weight: 400; letter-spacing: 0; }
|
| 84 |
.search-wrap { padding: 10px; border-bottom: 1px solid var(--border); }
|
| 85 |
+
.search-input { width: 100%; background: var(--bg); border: 1px solid var(--border-bright); color: var(--text-bright); font-family: 'Copper Black', serif; font-size: 14px; padding: 8px 10px 8px 30px; border-radius: 3px; outline: none; transition: border-color 0.2s; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' fill='%235a7080' viewBox='0 0 16 16'%3E%3Cpath d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.099zm-5.242 1.656a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: 10px center; }
|
| 86 |
.search-input:focus { border-color: var(--accent); }
|
| 87 |
.search-input::placeholder { color: var(--text-dim); }
|
| 88 |
.char-list { max-height: 520px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--border-bright) transparent; }
|
|
|
|
| 131 |
|
| 132 |
/* ACTIONS */
|
| 133 |
.actions-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
| 134 |
+
.btn { padding: 10px 22px; border: none; cursor: pointer; font-family: 'Copper Black', serif; font-size: 14px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; border-radius: 3px; transition: all 0.2s; display: flex; align-items: center; gap: 6px; }
|
| 135 |
.btn-primary { background: var(--accent); color: #000; }
|
| 136 |
.btn-primary:hover { background: #30dfff; box-shadow: 0 0 20px rgba(0,200,255,0.4); }
|
| 137 |
.btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--border-bright); }
|
|
|
|
| 151 |
.tool-badge.ok { background: rgba(61,220,151,0.1); color: var(--civilian); border: 1px solid rgba(61,220,151,0.3); }
|
| 152 |
.tool-badge.missing { background: rgba(255,68,85,0.1); color: var(--danger); border: 1px solid rgba(255,68,85,0.3); }
|
| 153 |
|
| 154 |
+
/* URL UPLOAD */
|
| 155 |
+
.url-upload-wrap { margin-top: 12px; }
|
| 156 |
+
.url-upload-inner { display: flex; align-items: center; gap: 10px; background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 10px 14px; }
|
| 157 |
+
.url-label { font-family: 'Share Tech Mono', monospace; font-size: 11px; color: var(--text-dim); letter-spacing: 1px; white-space: nowrap; }
|
| 158 |
+
.url-input { flex: 1; background: var(--bg); border: 1px solid var(--border-bright); color: var(--text-bright); font-family: 'Share Tech Mono', monospace; font-size: 12px; padding: 7px 10px; border-radius: 3px; outline: none; transition: border-color 0.2s; }
|
| 159 |
+
.url-input:focus { border-color: var(--accent); }
|
| 160 |
+
.url-input::placeholder { color: var(--text-dim); }
|
| 161 |
+
.url-btn { padding: 7px 16px; font-size: 12px; white-space: nowrap; }
|
| 162 |
+
|
| 163 |
/* TOAST */
|
| 164 |
.toast { position: fixed; bottom: 30px; right: 30px; background: var(--panel); border: 1px solid var(--accent); color: var(--text-bright); padding: 12px 20px; border-radius: 4px; font-size: 14px; font-weight: 600; letter-spacing: 1px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); transform: translateY(80px); opacity: 0; transition: all 0.3s cubic-bezier(0.34,1.56,0.64,1); z-index: 1000; }
|
| 165 |
.toast.show { transform: translateY(0); opacity: 1; }
|
|
|
|
| 199 |
<div class="upload-sub">or click to browse Β· .apk files only Β· max 200 MB</div>
|
| 200 |
<input type="file" id="apkInput" accept=".apk">
|
| 201 |
</div>
|
| 202 |
+
<!-- URL Upload -->
|
| 203 |
+
<div class="url-upload-wrap" id="urlUploadWrap">
|
| 204 |
+
<div class="url-upload-inner">
|
| 205 |
+
<span class="url-label">or load from URL</span>
|
| 206 |
+
<input class="url-input" type="text" id="apkUrlInput" placeholder="https://example.com/SuperCity.apk">
|
| 207 |
+
<button class="btn btn-secondary url-btn" onclick="uploadFromUrl()">β¬ Load URL</button>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
|
| 211 |
<div class="progress-wrap" id="progressWrap">
|
| 212 |
<div class="progress-label" id="progressLabel">Uploadingβ¦</div>
|
| 213 |
<div class="progress-bar-outer"><div class="progress-bar-inner" id="progressBar"></div></div>
|
|
|
|
| 384 |
<div class="file-info">π¦ <span class="file-info-name">${file.name}</span>
|
| 385 |
Β· ${characters.length} characters loaded Β· session: ${sessionToken.split('_')[0]}</div>`;
|
| 386 |
document.getElementById('editorLayout').style.display = 'grid';
|
| 387 |
+
document.getElementById('urlUploadWrap').style.display = 'none';
|
| 388 |
setStep(2);
|
| 389 |
showToast(`β Loaded ${characters.length} characters`);
|
| 390 |
} else {
|
|
|
|
| 398 |
xhr.send(form);
|
| 399 |
}
|
| 400 |
|
| 401 |
+
function uploadFromUrl() {
|
| 402 |
+
const url = document.getElementById('apkUrlInput').value.trim();
|
| 403 |
+
if (!url) { showToast('Enter a URL first', true); return; }
|
| 404 |
+
if (!url.toLowerCase().endsWith('.apk')) { showToast('URL must point to a .apk file', true); return; }
|
| 405 |
+
|
| 406 |
+
const wrap = document.getElementById('progressWrap');
|
| 407 |
+
const bar = document.getElementById('progressBar');
|
| 408 |
+
const label = document.getElementById('progressLabel');
|
| 409 |
+
wrap.style.display = 'block';
|
| 410 |
+
bar.style.width = '10%';
|
| 411 |
+
label.textContent = 'Fetching APK from URLβ¦';
|
| 412 |
+
|
| 413 |
+
fetch('/upload-url', {
|
| 414 |
+
method: 'POST',
|
| 415 |
+
headers: { 'Content-Type': 'application/json' },
|
| 416 |
+
body: JSON.stringify({ url }),
|
| 417 |
+
})
|
| 418 |
+
.then(res => {
|
| 419 |
+
bar.style.width = '80%';
|
| 420 |
+
label.textContent = 'Parsing charactersβ¦';
|
| 421 |
+
if (!res.ok) return res.json().then(e => { throw new Error(e.error); });
|
| 422 |
+
return res.json();
|
| 423 |
+
})
|
| 424 |
+
.then(data => {
|
| 425 |
+
bar.style.width = '100%';
|
| 426 |
+
sessionToken = data.token;
|
| 427 |
+
originalRaw = data.characters;
|
| 428 |
+
parseCharacters(data.characters);
|
| 429 |
+
wrap.style.display = 'none';
|
| 430 |
+
const fname = url.split('/').pop();
|
| 431 |
+
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 432 |
+
document.getElementById('fileInfoBar').innerHTML = `
|
| 433 |
+
<div class="file-info">π <span class="file-info-name">${fname}</span>
|
| 434 |
+
Β· ${characters.length} characters loaded Β· session: ${sessionToken.split('_')[0]}</div>`;
|
| 435 |
+
document.getElementById('editorLayout').style.display = 'grid';
|
| 436 |
+
document.getElementById('urlUploadWrap').style.display = 'none';
|
| 437 |
+
document.getElementById('uploadZone').style.display = 'none';
|
| 438 |
+
setStep(2);
|
| 439 |
+
showToast(`β Loaded ${characters.length} characters from URL`);
|
| 440 |
+
})
|
| 441 |
+
.catch(err => {
|
| 442 |
+
wrap.style.display = 'none';
|
| 443 |
+
bar.style.width = '0%';
|
| 444 |
+
showToast('Error: ' + err.message, true);
|
| 445 |
+
});
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
// ββ PARSE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 449 |
function parseCharacters(raw) {
|
| 450 |
characters = [];
|