Spaces:
Running
Running
LogicalTrue commited on
Commit ·
3a6bde5
1
Parent(s): bd8d298
Add interactive Cropper.js modal for cropping and framing subjects before 3D model generation
Browse files- frontend/app.js +98 -0
- frontend/index.html +38 -0
- frontend/viewer.html +129 -0
frontend/app.js
CHANGED
|
@@ -356,6 +356,104 @@ removePreviewBtn.addEventListener('click', (e) => {
|
|
| 356 |
generate3dBtn.disabled = true;
|
| 357 |
});
|
| 358 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
// 2D Image Generation
|
| 360 |
generate2dBtn.addEventListener('click', async () => {
|
| 361 |
const prompt = promptInput.value.trim();
|
|
|
|
| 356 |
generate3dBtn.disabled = true;
|
| 357 |
});
|
| 358 |
|
| 359 |
+
// Cropper.js Integration for PC Mode
|
| 360 |
+
let pcCropper = null;
|
| 361 |
+
|
| 362 |
+
const btnOpenCropPC = document.getElementById('btnOpenCropPC');
|
| 363 |
+
const cropModalOverlay = document.getElementById('cropModalOverlay');
|
| 364 |
+
const cropImageTarget = document.getElementById('cropImageTarget');
|
| 365 |
+
const btnCancelCrop = document.getElementById('btnCancelCrop');
|
| 366 |
+
const btnApplyCrop = document.getElementById('btnApplyCrop');
|
| 367 |
+
const btnCropAspectFree = document.getElementById('btnCropAspectFree');
|
| 368 |
+
const btnCropAspectSquare = document.getElementById('btnCropAspectSquare');
|
| 369 |
+
const btnCropReset = document.getElementById('btnCropReset');
|
| 370 |
+
|
| 371 |
+
function initCropperPC(imageUrl) {
|
| 372 |
+
if (!cropModalOverlay || !cropImageTarget) return;
|
| 373 |
+
|
| 374 |
+
cropImageTarget.src = imageUrl;
|
| 375 |
+
cropModalOverlay.style.display = 'flex';
|
| 376 |
+
|
| 377 |
+
if (pcCropper) {
|
| 378 |
+
pcCropper.destroy();
|
| 379 |
+
pcCropper = null;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
setTimeout(() => {
|
| 383 |
+
pcCropper = new Cropper(cropImageTarget, {
|
| 384 |
+
aspectRatio: 1,
|
| 385 |
+
viewMode: 1,
|
| 386 |
+
autoCropArea: 0.85,
|
| 387 |
+
responsive: true,
|
| 388 |
+
background: false
|
| 389 |
+
});
|
| 390 |
+
}, 150);
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
if (btnOpenCropPC) {
|
| 394 |
+
btnOpenCropPC.addEventListener('click', () => {
|
| 395 |
+
if (active2dImageURL) {
|
| 396 |
+
initCropperPC(active2dImageURL);
|
| 397 |
+
}
|
| 398 |
+
});
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
if (btnCancelCrop) {
|
| 402 |
+
btnCancelCrop.addEventListener('click', () => {
|
| 403 |
+
if (cropModalOverlay) cropModalOverlay.style.display = 'none';
|
| 404 |
+
if (pcCropper) {
|
| 405 |
+
pcCropper.destroy();
|
| 406 |
+
pcCropper = null;
|
| 407 |
+
}
|
| 408 |
+
});
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
if (btnCropAspectFree) {
|
| 412 |
+
btnCropAspectFree.addEventListener('click', () => {
|
| 413 |
+
if (pcCropper) pcCropper.setAspectRatio(NaN);
|
| 414 |
+
});
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
if (btnCropAspectSquare) {
|
| 418 |
+
btnCropAspectSquare.addEventListener('click', () => {
|
| 419 |
+
if (pcCropper) pcCropper.setAspectRatio(1);
|
| 420 |
+
});
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
if (btnCropReset) {
|
| 424 |
+
btnCropReset.addEventListener('click', () => {
|
| 425 |
+
if (pcCropper) pcCropper.reset();
|
| 426 |
+
});
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
if (btnApplyCrop) {
|
| 430 |
+
btnApplyCrop.addEventListener('click', () => {
|
| 431 |
+
if (!pcCropper) return;
|
| 432 |
+
|
| 433 |
+
const croppedCanvas = pcCropper.getCroppedCanvas({
|
| 434 |
+
width: 1024,
|
| 435 |
+
height: 1024,
|
| 436 |
+
imageSmoothingEnabled: true,
|
| 437 |
+
imageSmoothingQuality: 'high'
|
| 438 |
+
});
|
| 439 |
+
|
| 440 |
+
if (croppedCanvas) {
|
| 441 |
+
croppedCanvas.toBlob((blob) => {
|
| 442 |
+
if (blob) {
|
| 443 |
+
const croppedFile = new File([blob], 'cropped_image.png', { type: 'image/png' });
|
| 444 |
+
handleImageFile(croppedFile);
|
| 445 |
+
showCustomToast('Imagen recortada con éxito.');
|
| 446 |
+
}
|
| 447 |
+
if (cropModalOverlay) cropModalOverlay.style.display = 'none';
|
| 448 |
+
if (pcCropper) {
|
| 449 |
+
pcCropper.destroy();
|
| 450 |
+
pcCropper = null;
|
| 451 |
+
}
|
| 452 |
+
}, 'image/png');
|
| 453 |
+
}
|
| 454 |
+
});
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
// 2D Image Generation
|
| 458 |
generate2dBtn.addEventListener('click', async () => {
|
| 459 |
const prompt = promptInput.value.trim();
|
frontend/index.html
CHANGED
|
@@ -29,6 +29,10 @@
|
|
| 29 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
| 30 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/GLTFExporter.js"></script>
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
<!-- Custom Style -->
|
| 33 |
<link rel="stylesheet" href="styles.css">
|
| 34 |
</head>
|
|
@@ -115,6 +119,10 @@
|
|
| 115 |
<div class="preview-container" id="previewContainer" style="margin-top: 1rem;">
|
| 116 |
<img src="" alt="Preview" class="preview-img" id="imagePreview">
|
| 117 |
<button class="remove-preview" id="removePreviewBtn" title="Quitar imagen">×</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
</div>
|
| 119 |
</div>
|
| 120 |
|
|
@@ -713,6 +721,36 @@
|
|
| 713 |
</div>
|
| 714 |
</div>
|
| 715 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 716 |
<script type="module" src="app.js?v=118"></script>
|
| 717 |
<script>
|
| 718 |
// Initialize Lucide icons on page load
|
|
|
|
| 29 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
| 30 |
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/GLTFExporter.js"></script>
|
| 31 |
|
| 32 |
+
<!-- Cropper.js for Interactive Image Cropping -->
|
| 33 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
|
| 34 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
|
| 35 |
+
|
| 36 |
<!-- Custom Style -->
|
| 37 |
<link rel="stylesheet" href="styles.css">
|
| 38 |
</head>
|
|
|
|
| 119 |
<div class="preview-container" id="previewContainer" style="margin-top: 1rem;">
|
| 120 |
<img src="" alt="Preview" class="preview-img" id="imagePreview">
|
| 121 |
<button class="remove-preview" id="removePreviewBtn" title="Quitar imagen">×</button>
|
| 122 |
+
<button type="button" id="btnOpenCropPC" style="margin-top: 8px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 0.82rem; padding: 8px 12px; background: rgba(124, 58, 237, 0.15); border: 1px solid rgba(124, 58, 237, 0.4); color: #a78bfa; border-radius: 8px; cursor: pointer; font-weight: 600;">
|
| 123 |
+
<i data-lucide="crop" style="width: 14px; height: 14px;"></i>
|
| 124 |
+
<span>Enmarcar / Recortar Sujeto</span>
|
| 125 |
+
</button>
|
| 126 |
</div>
|
| 127 |
</div>
|
| 128 |
|
|
|
|
| 721 |
</div>
|
| 722 |
</div>
|
| 723 |
|
| 724 |
+
<!-- Interactive Crop Modal Overlay -->
|
| 725 |
+
<div id="cropModalOverlay" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 999999; background: rgba(10, 10, 15, 0.95); backdrop-filter: blur(12px); flex-direction: column; align-items: center; justify-content: space-between; padding: 16px; box-sizing: border-box;">
|
| 726 |
+
<!-- Modal Header -->
|
| 727 |
+
<div style="width: 100%; max-width: 650px; display: flex; justify-content: space-between; align-items: center; padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
| 728 |
+
<div style="font-family: 'Outfit', sans-serif; font-weight: 700; font-size: 1.1rem; color: #fff; display: flex; align-items: center; gap: 8px;">
|
| 729 |
+
<i data-lucide="crop" style="width: 18px; height: 18px; color: #a78bfa;"></i>
|
| 730 |
+
<span>Enmarcar / Recortar Sujeto</span>
|
| 731 |
+
</div>
|
| 732 |
+
<button id="btnCancelCrop" style="background: rgba(255,255,255,0.1); border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-weight: bold; font-size: 1rem; display: flex; align-items: center; justify-content: center;">✕</button>
|
| 733 |
+
</div>
|
| 734 |
+
|
| 735 |
+
<!-- Crop Canvas Viewport Container -->
|
| 736 |
+
<div style="width: 100%; max-width: 650px; height: calc(100vh - 180px); max-height: 520px; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; margin: 12px 0; background: #000; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1);">
|
| 737 |
+
<img id="cropImageTarget" src="" style="max-width: 100%; max-height: 100%;">
|
| 738 |
+
</div>
|
| 739 |
+
|
| 740 |
+
<!-- Modal Controls Footer -->
|
| 741 |
+
<div style="width: 100%; max-width: 650px; display: flex; flex-direction: column; gap: 10px;">
|
| 742 |
+
<div style="display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;">
|
| 743 |
+
<button type="button" id="btnCropAspectFree" style="background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">Libre</button>
|
| 744 |
+
<button type="button" id="btnCropAspectSquare" style="background: rgba(124,58,237,0.3); border: 1px solid #7c3aed; color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">1:1 Cuadrado (Recomendado)</button>
|
| 745 |
+
<button type="button" id="btnCropReset" style="background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">Reiniciar</button>
|
| 746 |
+
</div>
|
| 747 |
+
<button type="button" id="btnApplyCrop" style="width: 100%; background: linear-gradient(135deg, #10b981, #059669); color: #fff; border: none; padding: 12px; border-radius: 10px; font-weight: 700; font-size: 0.95rem; cursor: pointer; box-shadow: 0 4px 12px rgba(16,185,129,0.3); display: flex; align-items: center; justify-content: center; gap: 6px;">
|
| 748 |
+
<i data-lucide="check" style="width: 18px; height: 18px;"></i>
|
| 749 |
+
<span>Confirmar Recorte</span>
|
| 750 |
+
</button>
|
| 751 |
+
</div>
|
| 752 |
+
</div>
|
| 753 |
+
|
| 754 |
<script type="module" src="app.js?v=118"></script>
|
| 755 |
<script>
|
| 756 |
// Initialize Lucide icons on page load
|
frontend/viewer.html
CHANGED
|
@@ -6,6 +6,10 @@
|
|
| 6 |
<title>23DFactory - Visualizador Móvil</title>
|
| 7 |
<!-- Import Google's model-viewer component -->
|
| 8 |
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
<style>
|
| 10 |
body, html {
|
| 11 |
margin: 0;
|
|
@@ -361,6 +365,9 @@
|
|
| 361 |
<div id="imagePreviewContainer" style="display: none; position: relative;">
|
| 362 |
<img id="imagePreviewMobile" style="max-width: 100%; max-height: 250px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); margin: 0 auto; display: block;">
|
| 363 |
<button id="btnRemoveImageMobile" style="position: absolute; top: 8px; right: 8px; background: rgba(239,68,68,0.9); color: white; border: none; border-radius: 50%; width: 28px; height: 28px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 0.8rem;">✕</button>
|
|
|
|
|
|
|
|
|
|
| 364 |
</div>
|
| 365 |
</div>
|
| 366 |
|
|
@@ -455,6 +462,34 @@
|
|
| 455 |
</div>
|
| 456 |
</div>
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
<script>
|
| 459 |
const urlParams = new URLSearchParams(window.location.search);
|
| 460 |
const modelUrl = urlParams.get('model');
|
|
@@ -1075,6 +1110,100 @@
|
|
| 1075 |
});
|
| 1076 |
}
|
| 1077 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1078 |
// 2D AI Generation
|
| 1079 |
if (btnGen2DMobile) {
|
| 1080 |
btnGen2DMobile.addEventListener('click', async () => {
|
|
|
|
| 6 |
<title>23DFactory - Visualizador Móvil</title>
|
| 7 |
<!-- Import Google's model-viewer component -->
|
| 8 |
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
|
| 9 |
+
|
| 10 |
+
<!-- Cropper.js for Interactive Image Cropping -->
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css">
|
| 12 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
|
| 13 |
<style>
|
| 14 |
body, html {
|
| 15 |
margin: 0;
|
|
|
|
| 365 |
<div id="imagePreviewContainer" style="display: none; position: relative;">
|
| 366 |
<img id="imagePreviewMobile" style="max-width: 100%; max-height: 250px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); margin: 0 auto; display: block;">
|
| 367 |
<button id="btnRemoveImageMobile" style="position: absolute; top: 8px; right: 8px; background: rgba(239,68,68,0.9); color: white; border: none; border-radius: 50%; width: 28px; height: 28px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 0.8rem;">✕</button>
|
| 368 |
+
<button type="button" id="btnOpenCropMobile" style="margin-top: 10px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px; font-size: 0.85rem; padding: 10px; background: rgba(124, 58, 237, 0.2); border: 1px solid rgba(124, 58, 237, 0.5); color: #a78bfa; border-radius: 8px; cursor: pointer; font-weight: 600;">
|
| 369 |
+
✂️ Enmarcar / Recortar Sujeto
|
| 370 |
+
</button>
|
| 371 |
</div>
|
| 372 |
</div>
|
| 373 |
|
|
|
|
| 462 |
</div>
|
| 463 |
</div>
|
| 464 |
|
| 465 |
+
<!-- Interactive Crop Modal Overlay -->
|
| 466 |
+
<div id="cropModalOverlay" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 999999; background: rgba(10, 10, 15, 0.95); backdrop-filter: blur(12px); flex-direction: column; align-items: center; justify-content: space-between; padding: 16px; box-sizing: border-box;">
|
| 467 |
+
<!-- Modal Header -->
|
| 468 |
+
<div style="width: 100%; max-width: 600px; display: flex; justify-content: space-between; align-items: center; padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
| 469 |
+
<div style="font-weight: 700; font-size: 1rem; color: #fff; display: flex; align-items: center; gap: 8px;">
|
| 470 |
+
✂️ Enmarcar / Recortar Sujeto
|
| 471 |
+
</div>
|
| 472 |
+
<button id="btnCancelCrop" style="background: rgba(255,255,255,0.1); border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; font-weight: bold; font-size: 1rem; display: flex; align-items: center; justify-content: center;">✕</button>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
<!-- Crop Canvas Viewport Container -->
|
| 476 |
+
<div style="width: 100%; max-width: 600px; height: calc(100vh - 180px); max-height: 480px; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; margin: 12px 0; background: #000; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1);">
|
| 477 |
+
<img id="cropImageTarget" src="" style="max-width: 100%; max-height: 100%;">
|
| 478 |
+
</div>
|
| 479 |
+
|
| 480 |
+
<!-- Modal Controls Footer -->
|
| 481 |
+
<div style="width: 100%; max-width: 600px; display: flex; flex-direction: column; gap: 10px;">
|
| 482 |
+
<div style="display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;">
|
| 483 |
+
<button type="button" id="btnCropAspectFree" style="background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">Libre</button>
|
| 484 |
+
<button type="button" id="btnCropAspectSquare" style="background: rgba(124,58,237,0.3); border: 1px solid #7c3aed; color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">1:1 Cuadrado (Recomendado)</button>
|
| 485 |
+
<button type="button" id="btnCropReset" style="background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 6px 14px; border-radius: 16px; font-size: 0.78rem; cursor: pointer; font-weight: 600;">Reiniciar</button>
|
| 486 |
+
</div>
|
| 487 |
+
<button type="button" id="btnApplyCrop" style="width: 100%; background: linear-gradient(135deg, #10b981, #059669); color: #fff; border: none; padding: 12px; border-radius: 10px; font-weight: 700; font-size: 0.95rem; cursor: pointer; box-shadow: 0 4px 12px rgba(16,185,129,0.3); display: flex; align-items: center; justify-content: center; gap: 6px;">
|
| 488 |
+
✔ Confirmar Recorte
|
| 489 |
+
</button>
|
| 490 |
+
</div>
|
| 491 |
+
</div>
|
| 492 |
+
|
| 493 |
<script>
|
| 494 |
const urlParams = new URLSearchParams(window.location.search);
|
| 495 |
const modelUrl = urlParams.get('model');
|
|
|
|
| 1110 |
});
|
| 1111 |
}
|
| 1112 |
|
| 1113 |
+
// Cropper.js Integration for Mobile View
|
| 1114 |
+
let mobileCropper = null;
|
| 1115 |
+
|
| 1116 |
+
const btnOpenCropMobile = document.getElementById('btnOpenCropMobile');
|
| 1117 |
+
const cropModalOverlay = document.getElementById('cropModalOverlay');
|
| 1118 |
+
const cropImageTarget = document.getElementById('cropImageTarget');
|
| 1119 |
+
const btnCancelCrop = document.getElementById('btnCancelCrop');
|
| 1120 |
+
const btnApplyCrop = document.getElementById('btnApplyCrop');
|
| 1121 |
+
const btnCropAspectFree = document.getElementById('btnCropAspectFree');
|
| 1122 |
+
const btnCropAspectSquare = document.getElementById('btnCropAspectSquare');
|
| 1123 |
+
const btnCropReset = document.getElementById('btnCropReset');
|
| 1124 |
+
|
| 1125 |
+
function initCropperMobile(imageUrl) {
|
| 1126 |
+
if (!cropModalOverlay || !cropImageTarget) return;
|
| 1127 |
+
|
| 1128 |
+
cropImageTarget.src = imageUrl;
|
| 1129 |
+
cropModalOverlay.style.display = 'flex';
|
| 1130 |
+
|
| 1131 |
+
if (mobileCropper) {
|
| 1132 |
+
mobileCropper.destroy();
|
| 1133 |
+
mobileCropper = null;
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
setTimeout(() => {
|
| 1137 |
+
mobileCropper = new Cropper(cropImageTarget, {
|
| 1138 |
+
aspectRatio: 1,
|
| 1139 |
+
viewMode: 1,
|
| 1140 |
+
autoCropArea: 0.85,
|
| 1141 |
+
responsive: true,
|
| 1142 |
+
background: false
|
| 1143 |
+
});
|
| 1144 |
+
}, 150);
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
if (btnOpenCropMobile) {
|
| 1148 |
+
btnOpenCropMobile.addEventListener('click', () => {
|
| 1149 |
+
if (base64Image) {
|
| 1150 |
+
initCropperMobile(base64Image);
|
| 1151 |
+
}
|
| 1152 |
+
});
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
if (btnCancelCrop) {
|
| 1156 |
+
btnCancelCrop.addEventListener('click', () => {
|
| 1157 |
+
if (cropModalOverlay) cropModalOverlay.style.display = 'none';
|
| 1158 |
+
if (mobileCropper) {
|
| 1159 |
+
mobileCropper.destroy();
|
| 1160 |
+
mobileCropper = null;
|
| 1161 |
+
}
|
| 1162 |
+
});
|
| 1163 |
+
}
|
| 1164 |
+
|
| 1165 |
+
if (btnCropAspectFree) {
|
| 1166 |
+
btnCropAspectFree.addEventListener('click', () => {
|
| 1167 |
+
if (mobileCropper) mobileCropper.setAspectRatio(NaN);
|
| 1168 |
+
});
|
| 1169 |
+
}
|
| 1170 |
+
|
| 1171 |
+
if (btnCropAspectSquare) {
|
| 1172 |
+
btnCropAspectSquare.addEventListener('click', () => {
|
| 1173 |
+
if (mobileCropper) mobileCropper.setAspectRatio(1);
|
| 1174 |
+
});
|
| 1175 |
+
}
|
| 1176 |
+
|
| 1177 |
+
if (btnCropReset) {
|
| 1178 |
+
btnCropReset.addEventListener('click', () => {
|
| 1179 |
+
if (mobileCropper) mobileCropper.reset();
|
| 1180 |
+
});
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
if (btnApplyCrop) {
|
| 1184 |
+
btnApplyCrop.addEventListener('click', () => {
|
| 1185 |
+
if (!mobileCropper) return;
|
| 1186 |
+
|
| 1187 |
+
const croppedCanvas = mobileCropper.getCroppedCanvas({
|
| 1188 |
+
width: 1024,
|
| 1189 |
+
height: 1024,
|
| 1190 |
+
imageSmoothingEnabled: true,
|
| 1191 |
+
imageSmoothingQuality: 'high'
|
| 1192 |
+
});
|
| 1193 |
+
|
| 1194 |
+
if (croppedCanvas) {
|
| 1195 |
+
base64Image = croppedCanvas.toDataURL('image/png');
|
| 1196 |
+
if (imagePreviewMobile) imagePreviewMobile.src = base64Image;
|
| 1197 |
+
|
| 1198 |
+
if (cropModalOverlay) cropModalOverlay.style.display = 'none';
|
| 1199 |
+
if (mobileCropper) {
|
| 1200 |
+
mobileCropper.destroy();
|
| 1201 |
+
mobileCropper = null;
|
| 1202 |
+
}
|
| 1203 |
+
}
|
| 1204 |
+
});
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
// 2D AI Generation
|
| 1208 |
if (btnGen2DMobile) {
|
| 1209 |
btnGen2DMobile.addEventListener('click', async () => {
|