Spaces:
Runtime error
Runtime error
Update public/index.html
Browse files- public/index.html +18 -117
public/index.html
CHANGED
|
@@ -152,13 +152,8 @@
|
|
| 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 {
|
| 156 |
-
.url-
|
| 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; }
|
|
@@ -184,7 +179,7 @@
|
|
| 184 |
|
| 185 |
<!-- Steps -->
|
| 186 |
<div class="steps">
|
| 187 |
-
<div class="step active" id="step1"><span class="step-num">01</span>
|
| 188 |
<div class="step" id="step2"><span class="step-num">02</span> Edit Characters</div>
|
| 189 |
<div class="step" id="step3"><span class="step-num">03</span> Export Patched APK</div>
|
| 190 |
</div>
|
|
@@ -192,22 +187,6 @@
|
|
| 192 |
<!-- Tool status -->
|
| 193 |
<div class="tool-status" id="toolStatus"></div>
|
| 194 |
|
| 195 |
-
<!-- Upload -->
|
| 196 |
-
<div class="upload-zone" id="uploadZone">
|
| 197 |
-
<div class="upload-icon">π¦</div>
|
| 198 |
-
<div class="upload-title">Drop SuperCity.apk here</div>
|
| 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>
|
|
@@ -336,114 +315,36 @@ fetch('/tools').then(r => r.json()).then(tools => {
|
|
| 336 |
}
|
| 337 |
}).catch(() => {});
|
| 338 |
|
| 339 |
-
// ββ
|
| 340 |
-
|
| 341 |
-
const apkInput = document.getElementById('apkInput');
|
| 342 |
-
|
| 343 |
-
uploadZone.addEventListener('click', () => apkInput.click());
|
| 344 |
-
uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('drag-over'); });
|
| 345 |
-
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('drag-over'));
|
| 346 |
-
uploadZone.addEventListener('drop', e => { e.preventDefault(); uploadZone.classList.remove('drag-over'); if (e.dataTransfer.files[0]) uploadAPK(e.dataTransfer.files[0]); });
|
| 347 |
-
apkInput.addEventListener('change', () => { if (apkInput.files[0]) uploadAPK(apkInput.files[0]); });
|
| 348 |
-
|
| 349 |
-
function uploadAPK(file) {
|
| 350 |
-
if (!file.name.endsWith('.apk')) { showToast('Only .apk files', true); return; }
|
| 351 |
-
|
| 352 |
const wrap = document.getElementById('progressWrap');
|
| 353 |
const bar = document.getElementById('progressBar');
|
| 354 |
const label = document.getElementById('progressLabel');
|
| 355 |
wrap.style.display = 'block';
|
| 356 |
-
bar.style.width = '
|
| 357 |
-
label.textContent = '
|
| 358 |
-
|
| 359 |
-
const form = new FormData();
|
| 360 |
-
form.append('apk', file);
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
if (e.lengthComputable) {
|
| 367 |
-
const pct = Math.round(e.loaded / e.total * 80);
|
| 368 |
-
bar.style.width = pct + '%';
|
| 369 |
-
label.textContent = `Uploading⦠${pct}%`;
|
| 370 |
-
}
|
| 371 |
-
};
|
| 372 |
-
|
| 373 |
-
xhr.onload = () => {
|
| 374 |
-
bar.style.width = '100%';
|
| 375 |
-
label.textContent = 'Parsing charactersβ¦';
|
| 376 |
-
if (xhr.status === 200) {
|
| 377 |
-
const data = JSON.parse(xhr.responseText);
|
| 378 |
sessionToken = data.token;
|
| 379 |
originalRaw = data.characters;
|
| 380 |
parseCharacters(data.characters);
|
| 381 |
wrap.style.display = 'none';
|
| 382 |
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 383 |
document.getElementById('fileInfoBar').innerHTML = `
|
| 384 |
-
<div class="file-info">π¦ <span class="file-info-name">
|
| 385 |
-
Β· ${characters.length} characters loaded
|
| 386 |
document.getElementById('editorLayout').style.display = 'grid';
|
| 387 |
-
document.getElementById('urlUploadWrap').style.display = 'none';
|
| 388 |
setStep(2);
|
| 389 |
showToast(`β Loaded ${characters.length} characters`);
|
| 390 |
-
}
|
|
|
|
| 391 |
wrap.style.display = 'none';
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
};
|
| 396 |
-
|
| 397 |
-
xhr.onerror = () => { wrap.style.display = 'none'; showToast('Upload failed', true); };
|
| 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) {
|
|
|
|
| 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 { display: none; }
|
| 156 |
+
.url-btn { display: none; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
/* TOAST */
|
| 159 |
.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; }
|
|
|
|
| 179 |
|
| 180 |
<!-- Steps -->
|
| 181 |
<div class="steps">
|
| 182 |
+
<div class="step active" id="step1"><span class="step-num">01</span> Load APK</div>
|
| 183 |
<div class="step" id="step2"><span class="step-num">02</span> Edit Characters</div>
|
| 184 |
<div class="step" id="step3"><span class="step-num">03</span> Export Patched APK</div>
|
| 185 |
</div>
|
|
|
|
| 187 |
<!-- Tool status -->
|
| 188 |
<div class="tool-status" id="toolStatus"></div>
|
| 189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
<div class="progress-wrap" id="progressWrap">
|
| 191 |
<div class="progress-label" id="progressLabel">Uploadingβ¦</div>
|
| 192 |
<div class="progress-bar-outer"><div class="progress-bar-inner" id="progressBar"></div></div>
|
|
|
|
| 315 |
}
|
| 316 |
}).catch(() => {});
|
| 317 |
|
| 318 |
+
// ββ AUTO-INIT ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 319 |
+
(function initFromServer() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
const wrap = document.getElementById('progressWrap');
|
| 321 |
const bar = document.getElementById('progressBar');
|
| 322 |
const label = document.getElementById('progressLabel');
|
| 323 |
wrap.style.display = 'block';
|
| 324 |
+
bar.style.width = '30%';
|
| 325 |
+
label.textContent = 'Loading APKβ¦';
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
+
fetch('/init')
|
| 328 |
+
.then(r => { if (!r.ok) return r.json().then(e => { throw new Error(e.error); }); return r.json(); })
|
| 329 |
+
.then(data => {
|
| 330 |
+
bar.style.width = '100%';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
sessionToken = data.token;
|
| 332 |
originalRaw = data.characters;
|
| 333 |
parseCharacters(data.characters);
|
| 334 |
wrap.style.display = 'none';
|
| 335 |
document.getElementById('fileInfoBar').style.display = 'flex';
|
| 336 |
document.getElementById('fileInfoBar').innerHTML = `
|
| 337 |
+
<div class="file-info">π¦ <span class="file-info-name">super-city-2.010.64-mod-t-5play.apk</span>
|
| 338 |
+
Β· ${characters.length} characters loaded</div>`;
|
| 339 |
document.getElementById('editorLayout').style.display = 'grid';
|
|
|
|
| 340 |
setStep(2);
|
| 341 |
showToast(`β Loaded ${characters.length} characters`);
|
| 342 |
+
})
|
| 343 |
+
.catch(err => {
|
| 344 |
wrap.style.display = 'none';
|
| 345 |
+
showToast('Failed to load APK: ' + err.message, true);
|
| 346 |
+
});
|
| 347 |
+
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
// ββ PARSE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 350 |
function parseCharacters(raw) {
|