Spaces:
Runtime error
Runtime error
| {% extends "_layout.html" %} | |
| {% block title %}Try-On — {{ product.product_name }}{% endblock %} | |
| {% block head %} | |
| <style> | |
| .tryon-wrap { | |
| max-width: 900px; | |
| margin: 30px auto; | |
| padding: 0 16px; | |
| } | |
| /* ── Product card ── */ | |
| .product-card { | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.10); | |
| border-radius: 20px; | |
| padding: 28px; | |
| display: flex; | |
| gap: 32px; | |
| align-items: flex-start; | |
| flex-wrap: wrap; | |
| } | |
| .product-img-box { | |
| flex: 0 0 260px; | |
| background: rgba(0,0,0,0.25); | |
| border-radius: 16px; | |
| border: 1px solid rgba(255,255,255,0.08); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 16px; | |
| min-height: 280px; | |
| } | |
| .product-img-box img { | |
| max-width: 100%; | |
| max-height: 260px; | |
| object-fit: contain; | |
| filter: drop-shadow(0 10px 20px rgba(0,0,0,0.4)); | |
| } | |
| .product-info { flex: 1; min-width: 220px; } | |
| .product-info h2 { | |
| font-size: 22px; | |
| font-weight: 900; | |
| margin: 0 0 8px; | |
| } | |
| .product-info p { | |
| font-size: 13px; | |
| color: var(--muted); | |
| margin: 0 0 24px; | |
| } | |
| /* ── Method cards ── */ | |
| .method-row { | |
| display: flex; | |
| gap: 14px; | |
| flex-wrap: wrap; | |
| margin-bottom: 20px; | |
| } | |
| .method-card { | |
| flex: 1; | |
| min-width: 180px; | |
| border: 2px solid rgba(255,255,255,0.10); | |
| border-radius: 16px; | |
| padding: 20px 16px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: border-color .15s, background .15s; | |
| background: rgba(255,255,255,0.02); | |
| position: relative; | |
| } | |
| .method-card:hover { | |
| border-color: rgba(123,220,59,0.45); | |
| background: rgba(123,220,59,0.05); | |
| } | |
| .method-card.selected { | |
| border-color: rgba(123,220,59,0.7); | |
| background: rgba(123,220,59,0.08); | |
| } | |
| .method-icon { font-size: 32px; margin-bottom: 8px; } | |
| .method-title { font-size: 14px; font-weight: 800; } | |
| .method-sub { font-size: 11px; color: var(--muted); margin-top: 4px; } | |
| /* hidden file input inside upload card */ | |
| .method-card input[type="file"] { | |
| position: absolute; | |
| inset: 0; | |
| opacity: 0; | |
| cursor: pointer; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* selected file name */ | |
| #file-name { | |
| font-size: 12px; | |
| color: rgba(123,220,59,0.9); | |
| margin-top: 6px; | |
| min-height: 16px; | |
| } | |
| /* ── Buttons ── */ | |
| .btn-tryon { | |
| width: 100%; | |
| padding: 14px; | |
| border-radius: 14px; | |
| border: none; | |
| font-size: 15px; | |
| font-weight: 800; | |
| cursor: pointer; | |
| background: rgba(123,220,59,0.9); | |
| color: #000; | |
| transition: opacity .15s; | |
| margin-top: 4px; | |
| } | |
| .btn-tryon:hover { opacity: .85; } | |
| .btn-tryon:disabled { opacity: .4; cursor: not-allowed; } | |
| .btn-camera { | |
| display: block; | |
| width: 100%; | |
| padding: 13px; | |
| border-radius: 14px; | |
| border: 1px solid rgba(255,255,255,0.12); | |
| background: rgba(255,255,255,0.04); | |
| color: rgba(255,255,255,0.85); | |
| font-size: 14px; | |
| font-weight: 700; | |
| text-align: center; | |
| text-decoration: none; | |
| margin-top: 10px; | |
| transition: background .15s; | |
| } | |
| .btn-camera:hover { background: rgba(255,255,255,0.08); } | |
| /* ── Flash messages ── */ | |
| .flash-error { | |
| background: rgba(255,80,80,0.10); | |
| border: 1px solid rgba(255,80,80,0.30); | |
| border-radius: 12px; | |
| padding: 12px 16px; | |
| color: #ff8080; | |
| font-size: 13px; | |
| margin-bottom: 16px; | |
| } | |
| /* ── Result ── */ | |
| .result-card { | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(123,220,59,0.25); | |
| border-radius: 20px; | |
| padding: 24px; | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| .result-card h3 { | |
| font-size: 16px; | |
| font-weight: 800; | |
| margin: 0 0 16px; | |
| color: rgba(123,220,59,0.9); | |
| } | |
| .result-card img { | |
| max-width: 100%; | |
| max-height: 500px; | |
| border-radius: 14px; | |
| border: 1px solid rgba(255,255,255,0.10); | |
| object-fit: contain; | |
| } | |
| .preview-thumb { | |
| max-height: 120px; | |
| border-radius: 10px; | |
| margin-top: 10px; | |
| border: 1px solid rgba(255,255,255,0.10); | |
| display: none; | |
| } | |
| .loading-msg { | |
| display: none; | |
| color: rgba(123,220,59,0.8); | |
| font-size: 13px; | |
| margin-top: 10px; | |
| text-align: center; | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block body %} | |
| <div class="tryon-wrap"> | |
| {% with messages = get_flashed_messages(with_categories=true) %} | |
| {% for cat, msg in messages %} | |
| <div class="flash-error">⚠️ {{ msg }}</div> | |
| {% endfor %} | |
| {% endwith %} | |
| <div class="product-card"> | |
| <!-- Left: cloth image --> | |
| <div class="product-img-box"> | |
| <img src="{{ product.image_src }}" alt="{{ product.product_name }}"> | |
| </div> | |
| <!-- Right: try-on controls --> | |
| <div class="product-info"> | |
| <h2>{{ product.product_name }}</h2> | |
| <p>Choose how you want to try this on:</p> | |
| <!-- Upload form --> | |
| <form method="POST" | |
| enctype="multipart/form-data" | |
| action="{{ url_for('tryon_product', product_id=product.product_id) }}" | |
| id="tryon-form" | |
| onsubmit="showLoading()"> | |
| <div class="method-row"> | |
| <!-- Option 1: Upload photo --> | |
| <div class="method-card" id="upload-card"> | |
| <input type="file" | |
| name="person_image" | |
| id="person_image" | |
| accept="image/*" | |
| onchange="onFileSelected(this)"> | |
| <div class="method-icon">📁</div> | |
| <div class="method-title">Upload Photo</div> | |
| <div class="method-sub">Select from your device</div> | |
| <div id="file-name"></div> | |
| <img id="preview-thumb" class="preview-thumb" alt="preview"> | |
| </div> | |
| </div> | |
| <button class="btn-tryon" type="submit" id="btn-submit" disabled> | |
| 👗 Try On with Uploaded Photo | |
| </button> | |
| <div class="loading-msg" id="loading-msg"> | |
| ⏳ Processing your try-on… this may take 20–40 seconds | |
| </div> | |
| </form> | |
| <!-- Live camera (separate link, no form needed) --> | |
| <a class="btn-camera" | |
| href="{{ url_for('live_tryon_page', product_id=product.product_id) }}"> | |
| 📷 Try On with Live Camera | |
| </a> | |
| </div> | |
| </div> | |
| <!-- Result --> | |
| {% if result_image_url %} | |
| <div class="result-card"> | |
| <h3>✅ Try-On Result</h3> | |
| <img src="{{ result_image_url }}" alt="Try-On Result"> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| function onFileSelected(input) { | |
| const fileName = input.files[0]?.name; | |
| const fileNameEl = document.getElementById("file-name"); | |
| const submitBtn = document.getElementById("btn-submit"); | |
| const preview = document.getElementById("preview-thumb"); | |
| const card = document.getElementById("upload-card"); | |
| if (input.files[0]) { | |
| fileNameEl.textContent = "✅ " + fileName; | |
| submitBtn.disabled = false; | |
| card.classList.add("selected"); | |
| // Show image preview | |
| const reader = new FileReader(); | |
| reader.onload = e => { | |
| preview.src = e.target.result; | |
| preview.style.display = "block"; | |
| }; | |
| reader.readAsDataURL(input.files[0]); | |
| } | |
| } | |
| function showLoading() { | |
| document.getElementById("btn-submit").disabled = true; | |
| document.getElementById("btn-submit").textContent = "⏳ Processing…"; | |
| document.getElementById("loading-msg").style.display = "block"; | |
| } | |
| </script> | |
| {% endblock %} |