File size: 7,441 Bytes
f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 7edb31d f07b0b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>π Virtual Try-On</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',sans-serif;background:#0f0f0f;color:#fff;min-height:100vh}
header{background:linear-gradient(135deg,#e879f9,#818cf8);padding:28px;text-align:center}
header h1{font-size:2.2rem;font-weight:800;letter-spacing:-1px}
header p{opacity:.85;margin-top:8px;font-size:1rem}
.container{max-width:1080px;margin:32px auto;padding:0 20px}
.model-bar{text-align:center;padding:12px 20px;border-radius:10px;margin-bottom:24px;font-size:.9rem;font-weight:600}
.loading{background:#1c1917;color:#fbbf24;border:1px solid #78350f}
.ready{background:#052e16;color:#4ade80;border:1px solid #166534}
.errored{background:#1c0606;color:#f87171;border:1px solid #7f1d1d}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
@media(max-width:720px){.grid{grid-template-columns:1fr}}
.card{background:#161616;border-radius:16px;padding:20px;border:1px solid #272727}
.card h3{color:#c084fc;font-size:.95rem;margin-bottom:14px;text-transform:uppercase;letter-spacing:.05em}
.drop{border:2px dashed #333;border-radius:12px;min-height:240px;display:flex;
flex-direction:column;align-items:center;justify-content:center;gap:8px;
cursor:pointer;transition:.2s;padding:16px;position:relative}
.drop:hover{border-color:#c084fc;background:#1a1a1a}
.drop img{max-height:200px;max-width:100%;border-radius:8px;object-fit:contain}
.drop input{display:none}
.drop .em{font-size:2.8rem}
.drop .lbl{color:#555;font-size:.82rem;text-align:center}
.btn{width:100%;padding:14px;border:none;border-radius:12px;cursor:pointer;
font-size:1rem;font-weight:700;margin-top:14px;letter-spacing:.02em;
background:linear-gradient(135deg,#e879f9,#818cf8);color:#fff;transition:.2s}
.btn:hover{opacity:.85;transform:translateY(-1px)}
.btn:disabled{opacity:.35;cursor:not-allowed;transform:none}
.msg{text-align:center;margin-top:10px;font-size:.85rem;color:#888;min-height:18px}
.bar-wrap{height:4px;background:#222;border-radius:2px;margin-top:8px;overflow:hidden}
.bar{height:100%;width:0%;background:linear-gradient(90deg,#e879f9,#818cf8);
border-radius:2px;transition:width .8s ease}
.spin{display:none;width:36px;height:36px;border:3px solid #222;
border-top-color:#e879f9;border-radius:50%;animation:rot .7s linear infinite;margin:70px auto}
@keyframes rot{to{transform:rotate(360deg)}}
.res-img{width:100%;border-radius:10px;display:none}
.tip{background:#161616;border-radius:12px;padding:18px 20px;margin-top:24px;
border-left:4px solid #818cf8}
.tip p{color:#666;font-size:.85rem;line-height:1.7}
.tip b{color:#a5b4fc}
</style>
</head>
<body>
<header>
<h1>π Virtual Try-On</h1>
<p>Upload your photo + any garment β powered by AI running right here</p>
</header>
<div class="container">
<div class="model-bar loading" id="modelBar">β³ AI model loading β ready in ~2 minutes on first start...</div>
<div class="grid">
<!-- Person -->
<div class="card">
<h3>π€ Your Photo</h3>
<div class="drop" onclick="document.getElementById('pIn').click()">
<div class="em" id="pEm">π§</div>
<div class="lbl" id="pLbl">Click to upload<br>Full body Β· Front facing</div>
<img id="pImg"/>
<input type="file" id="pIn" accept="image/*" onchange="preview(this,'pImg','pEm','pLbl','person')"/>
</div>
</div>
<!-- Garment -->
<div class="card">
<h3>π Garment</h3>
<div class="drop" onclick="document.getElementById('gIn').click()">
<div class="em" id="gEm">π</div>
<div class="lbl" id="gLbl">Click to upload<br>White bg recommended</div>
<img id="gImg"/>
<input type="file" id="gIn" accept="image/*" onchange="preview(this,'gImg','gEm','gLbl','garment')"/>
</div>
<button class="btn" id="btn" onclick="run()" disabled>β¨ Try It On!</button>
<div class="msg" id="msg"></div>
<div class="bar-wrap"><div class="bar" id="bar"></div></div>
</div>
<!-- Result -->
<div class="card">
<h3>π Result</h3>
<div class="drop" style="cursor:default">
<div class="em" id="rEm">β¨</div>
<div class="lbl" id="rLbl">Your try-on result<br>will appear here</div>
<div class="spin" id="spin"></div>
<img class="res-img" id="rImg"/>
</div>
</div>
</div>
<div class="tip">
<p><b>Tips for best results:</b> Use a clear front-facing full-body photo with good lighting. Garment images on plain white background produce the most realistic try-on. The AI will place the garment on your upper body area automatically.</p>
</div>
</div>
<script>
let files={person:null,garment:null}, modelReady=false;
async function pollModel(){
try{
const d=await(await fetch('/status')).json();
const bar=document.getElementById('modelBar');
if(d.loaded){
bar.className='model-bar ready'; bar.textContent='β
AI Model Ready!';
modelReady=true; updateBtn();
} else if(d.error){
bar.className='model-bar errored'; bar.textContent='β '+d.error;
} else { setTimeout(pollModel,4000); }
}catch(e){setTimeout(pollModel,4000);}
}
pollModel();
function preview(input,imgId,emId,lblId,key){
const f=input.files[0]; if(!f)return;
files[key]=f;
const r=new FileReader();
r.onload=e=>{
document.getElementById(emId).style.display='none';
document.getElementById(lblId).style.display='none';
const i=document.getElementById(imgId);
i.src=e.target.result; i.style.display='block';
};
r.readAsDataURL(f); updateBtn();
}
function updateBtn(){
document.getElementById('btn').disabled=!(files.person&&files.garment&&modelReady);
}
async function run(){
const btn=document.getElementById('btn');
const msg=document.getElementById('msg');
const spin=document.getElementById('spin');
const rImg=document.getElementById('rImg');
const rEm=document.getElementById('rEm');
const rLbl=document.getElementById('rLbl');
const bar=document.getElementById('bar');
btn.disabled=true; msg.textContent='β³ Processing (~30-60s)...';
spin.style.display='block'; rImg.style.display='none';
rEm.style.display='none'; rLbl.style.display='none';
bar.style.width='0%';
let p=0; const iv=setInterval(()=>{p=Math.min(p+1.5,88);bar.style.width=p+'%';},1000);
const fd=new FormData();
fd.append('person',files.person);
fd.append('garment',files.garment);
try{
const res=await fetch('/tryon',{method:'POST',body:fd});
const d=await res.json();
clearInterval(iv); bar.style.width='100%';
spin.style.display='none';
if(d.status==='ok'){
rImg.src='data:image/png;base64,'+d.image;
rImg.style.display='block';
msg.textContent='β
Done! Download or try another outfit.';
} else if(d.status==='loading'){
msg.textContent='β³ Still warming up, retrying in 20s...';
rEm.style.display='block'; rLbl.style.display='block'; rEm.textContent='β³';
setTimeout(run,20000); return;
} else {
msg.textContent='β '+d.message;
rEm.style.display='block'; rLbl.style.display='block';
}
}catch(e){
clearInterval(iv); spin.style.display='none';
msg.textContent='β '+e.message;
rEm.style.display='block'; rLbl.style.display='block';
}
btn.disabled=false;
}
</script>
</body>
</html>
|