akswaroop commited on
Commit
2d0208d
·
1 Parent(s): 089564d

fix: auto-detect server URL, works on HuggingFace

Browse files
Files changed (1) hide show
  1. templates/index.html +32 -118
templates/index.html CHANGED
@@ -14,7 +14,6 @@
14
  --grid:repeating-conic-gradient(#1c1c2880 0% 25%,#13131a 0% 50%) 0 0/20px 20px;
15
  }
16
  body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden}
17
- /* NAV */
18
  nav{display:flex;align-items:center;justify-content:space-between;padding:16px 36px;border-bottom:1px solid var(--border);position:sticky;top:0;background:rgba(10,10,15,0.9);backdrop-filter:blur(20px);z-index:100}
19
  .logo{font-family:'Syne',sans-serif;font-weight:800;font-size:21px;display:flex;align-items:center;gap:10px}
20
  .logo-badge{background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
@@ -22,7 +21,6 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
22
  .status-dot{width:7px;height:7px;border-radius:50%;background:var(--border);transition:.3s}
23
  .status-dot.online{background:var(--success);box-shadow:0 0 6px var(--success)}
24
  .status-dot.offline{background:var(--danger)}
25
- /* HERO */
26
  .hero{text-align:center;padding:48px 20px 36px;position:relative}
27
  .hero::before{content:'';position:absolute;width:500px;height:500px;border-radius:50%;background:radial-gradient(circle,rgba(124,92,252,0.1) 0%,transparent 70%);top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none}
28
  .hero h1{font-family:'Syne',sans-serif;font-size:clamp(32px,5.5vw,64px);font-weight:800;line-height:1.05;letter-spacing:-2px;margin-bottom:12px}
@@ -31,19 +29,7 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
31
  .tech-row{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
32
  .tech-pill{padding:5px 14px;border-radius:50px;border:1px solid var(--border);background:var(--surface);color:var(--muted);font-size:12px;font-weight:500}
33
  .tech-pill.active{border-color:var(--accent);color:var(--accent);background:rgba(124,92,252,0.08)}
34
- /* WORKSPACE */
35
  .workspace{max-width:1080px;margin:0 auto;padding:0 20px 60px}
36
- /* SERVER BANNER */
37
- .server-banner{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 18px;border-radius:14px;border:1px solid var(--border);background:var(--surface);margin-bottom:24px;flex-wrap:wrap}
38
- .sb-left{display:flex;align-items:center;gap:10px}
39
- .sb-icon{font-size:20px}
40
- .sb-text{font-size:14px;color:var(--muted)}
41
- .sb-text strong{color:var(--text)}
42
- .server-url{flex:1;min-width:180px;max-width:300px;padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;font-family:'DM Sans',sans-serif}
43
- .server-url:focus{outline:none;border-color:var(--accent)}
44
- .test-btn{padding:8px 18px;background:var(--surface2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;transition:.2s}
45
- .test-btn:hover{border-color:var(--accent);color:var(--accent)}
46
- /* DROP ZONE */
47
  .drop-zone{border:2px dashed var(--border);border-radius:20px;background:var(--surface);padding:60px 40px;text-align:center;cursor:pointer;transition:.3s;position:relative}
48
  .drop-zone:hover,.drop-zone.over{border-color:var(--accent);background:rgba(124,92,252,0.05)}
49
  .drop-icon{width:60px;height:60px;border-radius:16px;background:var(--surface2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:26px}
@@ -54,13 +40,10 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
54
  .upload-btn{padding:11px 26px;background:var(--accent);color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;transition:.2s}
55
  .upload-btn:hover{background:#6b4de8;transform:translateY(-1px)}
56
  #fileInput{display:none}
57
- /* EDITOR */
58
  .editor{display:none}
59
  .editor.show{display:block}
60
- /* MAIN LAYOUT */
61
  .main-grid{display:grid;grid-template-columns:1fr 320px;gap:20px;margin-bottom:20px}
62
  @media(max-width:780px){.main-grid{grid-template-columns:1fr}}
63
- /* PANELS */
64
  .panel{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden}
65
  .panel-hd{padding:11px 16px;background:var(--surface2);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
66
  .panel-title{font-size:13px;font-weight:500;color:var(--muted);display:flex;align-items:center;gap:7px}
@@ -71,11 +54,9 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
71
  .panel-body{min-height:300px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;padding:12px}
72
  .panel-body.checker{background:var(--grid)}
73
  .panel-body img{max-width:100%;max-height:380px;object-fit:contain;display:block;margin:auto}
74
- /* SIDEBAR */
75
  .sidebar{display:flex;flex-direction:column;gap:14px}
76
  .ctrl-card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:16px}
77
  .ctrl-label{font-size:12px;font-weight:500;color:var(--muted);margin-bottom:10px;display:flex;align-items:center;gap:6px}
78
- /* MODEL SELECT */
79
  .model-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
80
  .model-btn{padding:10px 6px;border-radius:10px;border:1px solid var(--border);background:var(--surface2);cursor:pointer;text-align:center;transition:.2s;font-family:'DM Sans',sans-serif}
81
  .model-btn:hover{border-color:var(--accent)}
@@ -83,7 +64,6 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
83
  .model-btn .mb-icon{font-size:18px;display:block;margin-bottom:3px}
84
  .model-btn .mb-name{font-size:12px;font-weight:500;color:var(--text)}
85
  .model-btn .mb-desc{font-size:10px;color:var(--muted);margin-top:1px}
86
- /* BG COLOR */
87
  .swatch-row{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:8px}
88
  .swatch{width:30px;height:30px;border-radius:7px;border:2px solid transparent;cursor:pointer;transition:.15s;flex-shrink:0}
89
  .swatch:hover,.swatch.sel{border-color:var(--accent);transform:scale(1.1)}
@@ -91,11 +71,9 @@ nav{display:flex;align-items:center;justify-content:space-between;padding:16px 3
91
  .color-picker{width:34px;height:34px;border-radius:7px;border:1px solid var(--border);padding:2px;background:var(--surface2);cursor:pointer}
92
  .hex-input{flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:6px 10px;color:var(--text);font-size:13px;font-family:'DM Sans',sans-serif}
93
  .hex-input:focus{outline:none;border-color:var(--accent)}
94
- /* SLIDERS */
95
  .sl-row{margin-bottom:8px}
96
  .sl-hd{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);margin-bottom:4px}
97
  input[type=range]{width:100%;accent-color:var(--accent);height:3px}
98
- /* TOGGLE */
99
  .tog-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
100
  .tog-row:last-child{margin-bottom:0}
101
  .tog-label{font-size:13px;color:var(--muted)}
@@ -105,24 +83,19 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
105
  .tog-sl::before{content:'';position:absolute;width:15px;height:15px;border-radius:50%;background:#fff;left:3px;top:3px;transition:.3s}
106
  .toggle input:checked+.tog-sl{background:var(--accent)}
107
  .toggle input:checked+.tog-sl::before{left:20px}
108
- /* PROCESS BTN */
109
  .process-btn{width:100%;padding:14px;background:var(--accent);border:none;border-radius:12px;color:#fff;font-size:15px;font-weight:600;cursor:pointer;font-family:'Syne',sans-serif;transition:.2s;display:flex;align-items:center;justify-content:center;gap:8px;letter-spacing:.3px}
110
  .process-btn:hover{background:#6b4de8;transform:translateY(-1px)}
111
  .process-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
112
- /* PROGRESS */
113
  .progress-wrap{background:var(--surface2);border-radius:50px;height:6px;margin-top:10px;overflow:hidden;display:none}
114
  .progress-bar{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:50px;width:0;transition:.3s}
115
- /* OVERLAY */
116
  .proc-overlay{position:absolute;inset:0;background:rgba(10,10,15,0.88);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;backdrop-filter:blur(6px);display:none}
117
  .spinner{width:38px;height:38px;border-radius:50%;border:3px solid var(--border);border-top-color:var(--accent);animation:spin .75s linear infinite}
118
  @keyframes spin{to{transform:rotate(360deg)}}
119
  .proc-msg{font-size:14px;color:var(--muted)}
120
  .proc-sub{font-size:12px;color:var(--border)}
121
- /* STATS */
122
  .stats-row{display:flex;gap:16px;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--border);margin-top:8px}
123
  .stat{font-size:12px;color:var(--muted);display:flex;align-items:center;gap:4px}
124
  .stat strong{color:var(--text)}
125
- /* ACTION BAR */
126
  .action-bar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:12px 16px}
127
  .act-btn{padding:9px 18px;border-radius:9px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:13px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;transition:.2s;display:flex;align-items:center;gap:6px}
128
  .act-btn:hover{border-color:var(--accent);color:var(--accent)}
@@ -131,7 +104,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
131
  .act-btn:disabled{opacity:.3;cursor:not-allowed}
132
  .sel-fmt{padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:9px;color:var(--text);font-size:13px;font-family:'DM Sans',sans-serif;cursor:pointer}
133
  .sel-fmt:focus{outline:none;border-color:var(--accent)}
134
- /* COMPARE */
135
  .cmp-wrap{position:relative;width:100%;cursor:ew-resize;user-select:none;display:inline-block}
136
  .cmp-wrap img{display:block;width:100%;max-height:380px;object-fit:contain}
137
  .cmp-after{position:absolute;inset:0;overflow:hidden}
@@ -140,20 +112,16 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
140
  .cmp-handle{position:absolute;top:50%;transform:translate(-50%,-50%);width:30px;height:30px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:700}
141
  .cmp-labels{position:absolute;bottom:8px;left:0;right:0;display:flex;justify-content:space-between;padding:0 8px;pointer-events:none}
142
  .clabel{padding:2px 9px;border-radius:5px;font-size:11px;background:rgba(0,0,0,0.6);color:#fff}
143
- /* TABS */
144
  .tab-row{display:flex;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:4px;margin-bottom:16px;width:fit-content}
145
  .tab{padding:7px 18px;border-radius:8px;border:none;background:transparent;color:var(--muted);font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500;transition:.2s}
146
  .tab.active{background:var(--accent);color:#fff}
147
- /* HISTORY */
148
  .hist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:10px;margin-top:12px}
149
  .hist-item{border-radius:10px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:.2s;background:var(--grid);position:relative}
150
  .hist-item:hover{border-color:var(--accent);transform:translateY(-2px)}
151
  .hist-item img{width:100%;height:80px;object-fit:contain;display:block}
152
  .hist-item .hl{padding:4px 7px;font-size:10px;color:var(--muted);background:var(--surface2);border-top:1px solid var(--border);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
153
- /* PLACEHOLDER */
154
  .ph{color:var(--muted);font-size:13px;text-align:center;padding:30px}
155
  .ph-icon{font-size:28px;margin-bottom:8px}
156
- /* TOAST */
157
  #toast{position:fixed;bottom:24px;right:24px;padding:11px 18px;background:var(--surface2);border:1px solid var(--border);border-radius:11px;font-size:13px;color:var(--text);transform:translateY(80px);opacity:0;transition:.3s;z-index:999;display:flex;align-items:center;gap:7px;max-width:320px}
158
  #toast.show{transform:translateY(0);opacity:1}
159
  </style>
@@ -175,25 +143,13 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
175
  <span class="tech-pill active">⚡ rembg AI</span>
176
  <span class="tech-pill">🐍 Flask Backend</span>
177
  <span class="tech-pill">4 AI Models</span>
178
- <span class="tech-pill">100% Local</span>
179
  </div>
180
- <h1>CutIt out<br><span>Powered by Ak</span></h1>
181
- <p>Professional-grade background removal running on your own machine — no cloud, no limits.</p>
182
  </div>
183
 
184
  <div class="workspace">
185
-
186
- <!-- SERVER CONFIG -->
187
- <div class="server-banner">
188
- <div class="sb-left">
189
- <span class="sb-icon">🖥</span>
190
- <div class="sb-text"><strong>Flask Server URL</strong> — point to your running backend</div>
191
- </div>
192
- <input class="server-url" id="serverUrl" type="text" value="http://localhost:5000" placeholder="http://localhost:5000"/>
193
- <button class="test-btn" onclick="testServer()">🔌 Test Connection</button>
194
- </div>
195
-
196
- <!-- UPLOAD -->
197
  <div id="uploadSection">
198
  <div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()" ondragover="dov(event)" ondragleave="dol(event)" ondrop="dod(event)">
199
  <div class="drop-icon">📸</div>
@@ -208,9 +164,7 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
208
  <input type="file" id="fileInput" accept="image/*" onchange="loadFile(this.files[0])"/>
209
  </div>
210
 
211
- <!-- EDITOR -->
212
  <div class="editor" id="editor">
213
-
214
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:10px">
215
  <div class="tab-row">
216
  <button class="tab active" id="t-edit" onclick="sv('edit')">✏️ Edit</button>
@@ -219,10 +173,8 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
219
  <button class="act-btn" onclick="resetAll()" style="font-size:12px;padding:7px 14px">← New Image</button>
220
  </div>
221
 
222
- <!-- EDIT VIEW -->
223
  <div id="v-edit">
224
  <div class="main-grid">
225
- <!-- CANVAS COLUMN -->
226
  <div style="display:flex;flex-direction:column;gap:14px">
227
  <div class="panel">
228
  <div class="panel-hd">
@@ -247,18 +199,15 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
247
  </div>
248
  </div>
249
  </div>
250
- <!-- STATS -->
251
  <div class="stats-row" id="statsRow" style="display:none">
252
  <div class="stat">📐 <strong id="sDims">—</strong></div>
253
  <div class="stat">⏱ <strong id="sTime">—</strong>ms</div>
254
  <div class="stat">🤖 Model: <strong id="sModel">—</strong></div>
255
- <div class="stat" style="color:var(--success)" id="sDone" style="display:none">✓ Done!</div>
256
  </div>
257
  </div>
258
 
259
- <!-- SIDEBAR -->
260
  <div class="sidebar">
261
- <!-- MODEL -->
262
  <div class="ctrl-card">
263
  <div class="ctrl-label">🤖 AI Model</div>
264
  <div class="model-grid">
@@ -285,18 +234,17 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
285
  </div>
286
  </div>
287
 
288
- <!-- BACKGROUND -->
289
  <div class="ctrl-card">
290
  <div class="ctrl-label">���� Background</div>
291
  <div class="swatch-row">
292
- <div class="swatch sel" data-c="transparent" style="background:repeating-conic-gradient(#888 0% 25%,#ccc 0% 50%) 0 0/12px 12px" onclick="selSwatch('transparent',this)" title="Transparent"></div>
293
- <div class="swatch" data-c="#ffffff" style="background:#fff;border-color:#ddd" onclick="selSwatch('#ffffff',this)"></div>
294
- <div class="swatch" data-c="#000000" style="background:#000" onclick="selSwatch('#000000',this)"></div>
295
- <div class="swatch" data-c="#3b82f6" style="background:#3b82f6" onclick="selSwatch('#3b82f6',this)"></div>
296
- <div class="swatch" data-c="#22c55e" style="background:#22c55e" onclick="selSwatch('#22c55e',this)"></div>
297
- <div class="swatch" data-c="#f59e0b" style="background:#f59e0b" onclick="selSwatch('#f59e0b',this)"></div>
298
- <div class="swatch" data-c="#ec4899" style="background:#ec4899" onclick="selSwatch('#ec4899',this)"></div>
299
- <div class="swatch" data-c="#8b5cf6" style="background:#8b5cf6" onclick="selSwatch('#8b5cf6',this)"></div>
300
  </div>
301
  <div class="color-row">
302
  <input type="color" class="color-picker" id="cpicker" value="#7c5cfc" oninput="selSwatch(this.value,null)"/>
@@ -304,7 +252,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
304
  </div>
305
  </div>
306
 
307
- <!-- REFINEMENT -->
308
  <div class="ctrl-card">
309
  <div class="ctrl-label">🔧 Edge Refinement</div>
310
  <div class="sl-row">
@@ -313,7 +260,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
313
  </div>
314
  </div>
315
 
316
- <!-- SHADOW -->
317
  <div class="ctrl-card">
318
  <div class="ctrl-label">✨ Effects</div>
319
  <div class="tog-row">
@@ -332,7 +278,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
332
  </div>
333
  </div>
334
 
335
- <!-- OUTPUT -->
336
  <div class="ctrl-card">
337
  <div class="ctrl-label">📤 Output</div>
338
  <div style="display:flex;gap:8px;margin-bottom:10px">
@@ -349,7 +294,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
349
  </div>
350
  </div>
351
 
352
- <!-- PROCESS -->
353
  <button class="process-btn" id="procBtn" onclick="processImage()">
354
  <span id="procIcon">✨</span> <span id="procTxt">Remove Background</span>
355
  </button>
@@ -359,7 +303,6 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
359
  </div>
360
  </div>
361
 
362
- <!-- ACTION BAR -->
363
  <div class="action-bar">
364
  <button class="act-btn" onclick="copyImg()" id="copyBtn" disabled>📋 Copy PNG</button>
365
  <div style="flex:1"></div>
@@ -367,14 +310,12 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
367
  <button class="act-btn dl" onclick="dlResult()" id="dlBtn" disabled>⬇ Download</button>
368
  </div>
369
 
370
- <!-- HISTORY -->
371
  <div id="histSection" style="margin-top:24px;display:none">
372
  <div style="font-family:'Syne',sans-serif;font-weight:700;font-size:15px;color:var(--muted);margin-bottom:4px">History <span id="histCount" style="font-weight:400;font-size:13px"></span></div>
373
  <div class="hist-grid" id="histGrid"></div>
374
  </div>
375
  </div>
376
 
377
- <!-- COMPARE VIEW -->
378
  <div id="v-compare" style="display:none">
379
  <div class="panel">
380
  <div class="panel-hd"><span class="panel-title">⚡ Before / After — drag to compare</span></div>
@@ -389,7 +330,12 @@ input[type=range]{width:100%;accent-color:var(--accent);height:3px}
389
  <div id="toast"><span id="tIcon"></span><span id="tMsg"></span></div>
390
 
391
  <script>
392
- let serverUrl = 'http://localhost:5000';
 
 
 
 
 
393
  let origFile = null, origDataUrl = null;
394
  let resultBlob = null, resultUrl = null;
395
  let selModel = 'general';
@@ -397,33 +343,28 @@ let selBg = 'transparent';
397
  let history = [];
398
  let curView = 'edit';
399
 
400
- // ── SERVER ──────────────────────────────────────────────
401
- function getServer(){ return document.getElementById('serverUrl').value.replace(/\/$/,''); }
402
-
403
  async function testServer(){
404
  const url = getServer();
405
  const dot = document.getElementById('statusDot');
406
  const txt = document.getElementById('statusText');
407
- dot.className = 'status-dot'; txt.textContent = 'Testing…';
408
  try {
409
- const r = await fetch(url+'/health', {signal: AbortSignal.timeout(4000)});
410
  const d = await r.json();
411
  dot.className = 'status-dot online';
412
  txt.textContent = 'Server online ✓';
413
- toast('✓','Flask server connected! Models: '+d.models.join(', '),'success');
414
  } catch(e){
415
  dot.className = 'status-dot offline';
416
  txt.textContent = 'Server offline';
417
- toast('⚠','Cannot reach server at '+url,'error');
418
  }
419
  }
420
 
421
- // ── DRAG & DROP ─────────────────────────────────────────
422
  function dov(e){ e.preventDefault(); document.getElementById('dropZone').classList.add('over'); }
423
  function dol(){ document.getElementById('dropZone').classList.remove('over'); }
424
  function dod(e){ e.preventDefault(); dol(); const f=e.dataTransfer.files[0]; if(f&&f.type.startsWith('image/')) loadFile(f); }
425
 
426
- // ── LOAD ────────────────────────────────────────────────
427
  function loadFile(file){
428
  if(!file) return;
429
  origFile = file;
@@ -432,11 +373,9 @@ function loadFile(file){
432
  origDataUrl = e.target.result;
433
  document.getElementById('uploadSection').style.display='none';
434
  document.getElementById('editor').classList.add('show');
435
- // show original
436
  const op = document.getElementById('origPanel');
437
  op.innerHTML = `<img src="${origDataUrl}"/>`;
438
  document.getElementById('imgMeta').textContent = file.name+' · '+(file.size<1048576?(file.size/1024).toFixed(1)+'KB':(file.size/1048576).toFixed(2)+'MB');
439
- // reset result
440
  document.getElementById('resPanel').innerHTML = `<div class="ph" id="resPlaceholder"><div class="ph-icon">✨</div>Click "Remove Background"</div><div class="proc-overlay" id="procOverlay"><div class="spinner"></div><div class="proc-msg">Processing with rembg…</div><div class="proc-sub" id="procSub">Loading model</div></div>`;
441
  resultBlob=null; resultUrl=null;
442
  document.getElementById('dlBtn').disabled=true;
@@ -452,7 +391,6 @@ function resetAll(){
452
  document.getElementById('editor').classList.remove('show');
453
  }
454
 
455
- // ── PROCESS ─────────────────────────────────────────────
456
  async function processImage(){
457
  if(!origFile) return toast('⚠','Upload an image first','error');
458
  const url = getServer();
@@ -462,13 +400,12 @@ async function processImage(){
462
  document.getElementById('procIcon').textContent='⏳';
463
  document.getElementById('procTxt').textContent='Sending to rembg…';
464
  overlay.style.display='flex';
465
- document.getElementById('resPlaceholder') && (document.getElementById('resPlaceholder').style.display='none');
 
466
 
467
- // Progress pulse
468
  const pw = document.getElementById('progWrap'); pw.style.display='block';
469
  const pb = document.getElementById('progBar');
470
  let prog=0, pInterval = setInterval(()=>{ prog=Math.min(prog+Math.random()*8,88); pb.style.width=prog+'%'; },300);
471
-
472
  const msgs=['Loading AI model…','Detecting edges…','Separating subject…','Refining mask…','Compositing result…'];
473
  let mi=0, mInterval = setInterval(()=>{ const el=document.getElementById('procSub'); if(el) el.textContent=msgs[mi++%msgs.length]; },1500);
474
 
@@ -494,7 +431,6 @@ async function processImage(){
494
 
495
  resultBlob = blob;
496
  resultUrl = URL.createObjectURL(blob);
497
-
498
  const elapsed = resp.headers.get('X-Processing-Time') || (Date.now()-t0);
499
  const w = resp.headers.get('X-Image-Width')||'—';
500
  const h = resp.headers.get('X-Image-Height')||'—';
@@ -510,14 +446,12 @@ async function processImage(){
510
  document.getElementById('sDims').textContent=w+'×'+h;
511
  document.getElementById('sTime').textContent=elapsed;
512
  document.getElementById('sModel').textContent=selModel;
513
- document.getElementById('sDone').style.display='flex';
514
  document.getElementById('resMeta').textContent=w+'×'+h+' '+document.getElementById('outFmt').value.toUpperCase();
515
-
516
  document.getElementById('dlBtn').disabled=false;
517
  document.getElementById('copyBtn').disabled=false;
518
  addHistory(resultUrl, origFile.name);
519
  if(curView==='compare') buildCompare();
520
- toast('✓','Background removed by rembg!','success');
521
  } catch(e){
522
  clearInterval(pInterval); clearInterval(mInterval);
523
  pw.style.display='none';
@@ -531,7 +465,6 @@ async function processImage(){
531
  document.getElementById('procTxt').textContent='Remove Background';
532
  }
533
 
534
- // ── CONTROLS ────────────────────────────────────────────
535
  function setModel(m,el){
536
  selModel=m;
537
  document.querySelectorAll('.model-btn').forEach(b=>b.classList.remove('active'));
@@ -544,19 +477,14 @@ function selSwatch(c,el){
544
  document.getElementById('hexIn').value = c==='transparent'?'':c;
545
  if(c!=='transparent'&&c.startsWith('#')) document.getElementById('cpicker').value=c;
546
  }
547
- function onHex(v){
548
- if(/^#[0-9a-fA-F]{6}$/.test(v)) selSwatch(v,null);
549
- }
550
  function toggleShadowOpts(on){ document.getElementById('shadowOpts').style.display=on?'block':'none'; }
551
 
552
- // ── DOWNLOAD ────────────────────────────────────────────
553
  function dlResult(){
554
  if(!resultBlob) return;
555
  const fmt = document.getElementById('outFmt').value;
556
  const a = document.createElement('a');
557
- a.href = resultUrl;
558
- a.download = 'cutout.'+fmt;
559
- a.click();
560
  toast('⬇','Downloaded!','success');
561
  }
562
 
@@ -565,20 +493,17 @@ async function copyImg(){
565
  try {
566
  const png = resultBlob.type==='image/png' ? resultBlob : await convertToPng(resultBlob);
567
  await navigator.clipboard.write([new ClipboardItem({'image/png':png})]);
568
- toast('📋','Copied to clipboard!','success');
569
  } catch(e){ toast('⚠','Copy failed: '+e.message,'error'); }
570
  }
571
-
572
  async function convertToPng(blob){
573
  const url=URL.createObjectURL(blob);
574
- const img=await loadImg(url);
575
  const c=document.createElement('canvas'); c.width=img.width; c.height=img.height;
576
  c.getContext('2d').drawImage(img,0,0);
577
  return new Promise(res=>c.toBlob(res,'image/png'));
578
  }
579
- function loadImg(src){ return new Promise((res,rej)=>{ const i=new Image(); i.onload=()=>res(i); i.onerror=rej; i.src=src; }); }
580
 
581
- // ── COMPARE ─────────────────────────────────────────────
582
  function sv(v){
583
  curView=v;
584
  ['edit','compare'].forEach(x=>{ document.getElementById('v-'+x).style.display=x===v?'block':'none'; document.getElementById('t-'+x).classList.toggle('active',x===v); });
@@ -589,23 +514,14 @@ function buildCompare(){
589
  const c=document.getElementById('cmpContainer');
590
  if(!resultUrl||!origDataUrl){ c.innerHTML='<div class="ph"><div class="ph-icon">⚡</div>Process an image first</div>'; return; }
591
  c.innerHTML=`<div class="cmp-wrap" id="cw" style="max-width:640px;width:100%"><img src="${origDataUrl}" id="cb"/><div class="cmp-after" id="ca" style="clip-path:inset(0 50% 0 0)"><img src="${resultUrl}"/></div><div class="cmp-div" id="cd" style="left:50%"><div class="cmp-handle">↔</div></div><div class="cmp-labels"><span class="clabel">Original</span><span class="clabel">Result</span></div></div>`;
592
- const wrap=document.getElementById('cw'), after=document.getElementById('ca'), div=document.getElementById('cd');
593
  let drag=false;
594
  wrap.addEventListener('mousedown',()=>drag=true);
595
  document.addEventListener('mouseup',()=>drag=false);
596
- document.addEventListener('mousemove',e=>{
597
- if(!drag)return; const r=wrap.getBoundingClientRect();
598
- const p=Math.max(0,Math.min(100,((e.clientX-r.left)/r.width)*100));
599
- div.style.left=p+'%'; after.style.clipPath=`inset(0 ${100-p}% 0 0)`;
600
- });
601
- wrap.addEventListener('touchmove',e=>{
602
- const r=wrap.getBoundingClientRect();
603
- const p=Math.max(0,Math.min(100,((e.touches[0].clientX-r.left)/r.width)*100));
604
- div.style.left=p+'%'; after.style.clipPath=`inset(0 ${100-p}% 0 0)`;
605
- },{passive:true});
606
  }
607
 
608
- // ── HISTORY ────────────────────────────��────────────────
609
  function addHistory(url,name){
610
  history.unshift({url,name});
611
  if(history.length>16) history.pop();
@@ -623,7 +539,6 @@ function restoreHist(i){
623
  toast('↩','Restored from history','success');
624
  }
625
 
626
- // ── TOAST ───────────────────────────────────────────────
627
  function toast(icon,msg,type=''){
628
  const t=document.getElementById('toast');
629
  document.getElementById('tIcon').textContent=icon;
@@ -632,7 +547,6 @@ function toast(icon,msg,type=''){
632
  t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3500);
633
  }
634
 
635
- // ── INIT ────────────────────────────────────────────────
636
  testServer();
637
  </script>
638
  </body>
 
14
  --grid:repeating-conic-gradient(#1c1c2880 0% 25%,#13131a 0% 50%) 0 0/20px 20px;
15
  }
16
  body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden}
 
17
  nav{display:flex;align-items:center;justify-content:space-between;padding:16px 36px;border-bottom:1px solid var(--border);position:sticky;top:0;background:rgba(10,10,15,0.9);backdrop-filter:blur(20px);z-index:100}
18
  .logo{font-family:'Syne',sans-serif;font-weight:800;font-size:21px;display:flex;align-items:center;gap:10px}
19
  .logo-badge{background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
 
21
  .status-dot{width:7px;height:7px;border-radius:50%;background:var(--border);transition:.3s}
22
  .status-dot.online{background:var(--success);box-shadow:0 0 6px var(--success)}
23
  .status-dot.offline{background:var(--danger)}
 
24
  .hero{text-align:center;padding:48px 20px 36px;position:relative}
25
  .hero::before{content:'';position:absolute;width:500px;height:500px;border-radius:50%;background:radial-gradient(circle,rgba(124,92,252,0.1) 0%,transparent 70%);top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none}
26
  .hero h1{font-family:'Syne',sans-serif;font-size:clamp(32px,5.5vw,64px);font-weight:800;line-height:1.05;letter-spacing:-2px;margin-bottom:12px}
 
29
  .tech-row{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
30
  .tech-pill{padding:5px 14px;border-radius:50px;border:1px solid var(--border);background:var(--surface);color:var(--muted);font-size:12px;font-weight:500}
31
  .tech-pill.active{border-color:var(--accent);color:var(--accent);background:rgba(124,92,252,0.08)}
 
32
  .workspace{max-width:1080px;margin:0 auto;padding:0 20px 60px}
 
 
 
 
 
 
 
 
 
 
 
33
  .drop-zone{border:2px dashed var(--border);border-radius:20px;background:var(--surface);padding:60px 40px;text-align:center;cursor:pointer;transition:.3s;position:relative}
34
  .drop-zone:hover,.drop-zone.over{border-color:var(--accent);background:rgba(124,92,252,0.05)}
35
  .drop-icon{width:60px;height:60px;border-radius:16px;background:var(--surface2);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:26px}
 
40
  .upload-btn{padding:11px 26px;background:var(--accent);color:#fff;border:none;border-radius:10px;font-size:14px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;transition:.2s}
41
  .upload-btn:hover{background:#6b4de8;transform:translateY(-1px)}
42
  #fileInput{display:none}
 
43
  .editor{display:none}
44
  .editor.show{display:block}
 
45
  .main-grid{display:grid;grid-template-columns:1fr 320px;gap:20px;margin-bottom:20px}
46
  @media(max-width:780px){.main-grid{grid-template-columns:1fr}}
 
47
  .panel{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden}
48
  .panel-hd{padding:11px 16px;background:var(--surface2);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
49
  .panel-title{font-size:13px;font-weight:500;color:var(--muted);display:flex;align-items:center;gap:7px}
 
54
  .panel-body{min-height:300px;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;padding:12px}
55
  .panel-body.checker{background:var(--grid)}
56
  .panel-body img{max-width:100%;max-height:380px;object-fit:contain;display:block;margin:auto}
 
57
  .sidebar{display:flex;flex-direction:column;gap:14px}
58
  .ctrl-card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:16px}
59
  .ctrl-label{font-size:12px;font-weight:500;color:var(--muted);margin-bottom:10px;display:flex;align-items:center;gap:6px}
 
60
  .model-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
61
  .model-btn{padding:10px 6px;border-radius:10px;border:1px solid var(--border);background:var(--surface2);cursor:pointer;text-align:center;transition:.2s;font-family:'DM Sans',sans-serif}
62
  .model-btn:hover{border-color:var(--accent)}
 
64
  .model-btn .mb-icon{font-size:18px;display:block;margin-bottom:3px}
65
  .model-btn .mb-name{font-size:12px;font-weight:500;color:var(--text)}
66
  .model-btn .mb-desc{font-size:10px;color:var(--muted);margin-top:1px}
 
67
  .swatch-row{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:8px}
68
  .swatch{width:30px;height:30px;border-radius:7px;border:2px solid transparent;cursor:pointer;transition:.15s;flex-shrink:0}
69
  .swatch:hover,.swatch.sel{border-color:var(--accent);transform:scale(1.1)}
 
71
  .color-picker{width:34px;height:34px;border-radius:7px;border:1px solid var(--border);padding:2px;background:var(--surface2);cursor:pointer}
72
  .hex-input{flex:1;background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:6px 10px;color:var(--text);font-size:13px;font-family:'DM Sans',sans-serif}
73
  .hex-input:focus{outline:none;border-color:var(--accent)}
 
74
  .sl-row{margin-bottom:8px}
75
  .sl-hd{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);margin-bottom:4px}
76
  input[type=range]{width:100%;accent-color:var(--accent);height:3px}
 
77
  .tog-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
78
  .tog-row:last-child{margin-bottom:0}
79
  .tog-label{font-size:13px;color:var(--muted)}
 
83
  .tog-sl::before{content:'';position:absolute;width:15px;height:15px;border-radius:50%;background:#fff;left:3px;top:3px;transition:.3s}
84
  .toggle input:checked+.tog-sl{background:var(--accent)}
85
  .toggle input:checked+.tog-sl::before{left:20px}
 
86
  .process-btn{width:100%;padding:14px;background:var(--accent);border:none;border-radius:12px;color:#fff;font-size:15px;font-weight:600;cursor:pointer;font-family:'Syne',sans-serif;transition:.2s;display:flex;align-items:center;justify-content:center;gap:8px;letter-spacing:.3px}
87
  .process-btn:hover{background:#6b4de8;transform:translateY(-1px)}
88
  .process-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
 
89
  .progress-wrap{background:var(--surface2);border-radius:50px;height:6px;margin-top:10px;overflow:hidden;display:none}
90
  .progress-bar{height:100%;background:linear-gradient(90deg,var(--accent),var(--accent2));border-radius:50px;width:0;transition:.3s}
 
91
  .proc-overlay{position:absolute;inset:0;background:rgba(10,10,15,0.88);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;backdrop-filter:blur(6px);display:none}
92
  .spinner{width:38px;height:38px;border-radius:50%;border:3px solid var(--border);border-top-color:var(--accent);animation:spin .75s linear infinite}
93
  @keyframes spin{to{transform:rotate(360deg)}}
94
  .proc-msg{font-size:14px;color:var(--muted)}
95
  .proc-sub{font-size:12px;color:var(--border)}
 
96
  .stats-row{display:flex;gap:16px;flex-wrap:wrap;padding:10px 0;border-top:1px solid var(--border);margin-top:8px}
97
  .stat{font-size:12px;color:var(--muted);display:flex;align-items:center;gap:4px}
98
  .stat strong{color:var(--text)}
 
99
  .action-bar{display:flex;gap:10px;flex-wrap:wrap;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:12px 16px}
100
  .act-btn{padding:9px 18px;border-radius:9px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-size:13px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;transition:.2s;display:flex;align-items:center;gap:6px}
101
  .act-btn:hover{border-color:var(--accent);color:var(--accent)}
 
104
  .act-btn:disabled{opacity:.3;cursor:not-allowed}
105
  .sel-fmt{padding:8px 12px;background:var(--surface2);border:1px solid var(--border);border-radius:9px;color:var(--text);font-size:13px;font-family:'DM Sans',sans-serif;cursor:pointer}
106
  .sel-fmt:focus{outline:none;border-color:var(--accent)}
 
107
  .cmp-wrap{position:relative;width:100%;cursor:ew-resize;user-select:none;display:inline-block}
108
  .cmp-wrap img{display:block;width:100%;max-height:380px;object-fit:contain}
109
  .cmp-after{position:absolute;inset:0;overflow:hidden}
 
112
  .cmp-handle{position:absolute;top:50%;transform:translate(-50%,-50%);width:30px;height:30px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:700}
113
  .cmp-labels{position:absolute;bottom:8px;left:0;right:0;display:flex;justify-content:space-between;padding:0 8px;pointer-events:none}
114
  .clabel{padding:2px 9px;border-radius:5px;font-size:11px;background:rgba(0,0,0,0.6);color:#fff}
 
115
  .tab-row{display:flex;gap:4px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:4px;margin-bottom:16px;width:fit-content}
116
  .tab{padding:7px 18px;border-radius:8px;border:none;background:transparent;color:var(--muted);font-size:13px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500;transition:.2s}
117
  .tab.active{background:var(--accent);color:#fff}
 
118
  .hist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:10px;margin-top:12px}
119
  .hist-item{border-radius:10px;overflow:hidden;border:1px solid var(--border);cursor:pointer;transition:.2s;background:var(--grid);position:relative}
120
  .hist-item:hover{border-color:var(--accent);transform:translateY(-2px)}
121
  .hist-item img{width:100%;height:80px;object-fit:contain;display:block}
122
  .hist-item .hl{padding:4px 7px;font-size:10px;color:var(--muted);background:var(--surface2);border-top:1px solid var(--border);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
 
123
  .ph{color:var(--muted);font-size:13px;text-align:center;padding:30px}
124
  .ph-icon{font-size:28px;margin-bottom:8px}
 
125
  #toast{position:fixed;bottom:24px;right:24px;padding:11px 18px;background:var(--surface2);border:1px solid var(--border);border-radius:11px;font-size:13px;color:var(--text);transform:translateY(80px);opacity:0;transition:.3s;z-index:999;display:flex;align-items:center;gap:7px;max-width:320px}
126
  #toast.show{transform:translateY(0);opacity:1}
127
  </style>
 
143
  <span class="tech-pill active">⚡ rembg AI</span>
144
  <span class="tech-pill">🐍 Flask Backend</span>
145
  <span class="tech-pill">4 AI Models</span>
146
+ <span class="tech-pill">100% Free</span>
147
  </div>
148
+ <h1>Background Removal<br><span>Powered by rembg</span></h1>
149
+ <p>Professional-grade AI background removal — no limits, no watermarks.</p>
150
  </div>
151
 
152
  <div class="workspace">
 
 
 
 
 
 
 
 
 
 
 
 
153
  <div id="uploadSection">
154
  <div class="drop-zone" id="dropZone" onclick="document.getElementById('fileInput').click()" ondragover="dov(event)" ondragleave="dol(event)" ondrop="dod(event)">
155
  <div class="drop-icon">📸</div>
 
164
  <input type="file" id="fileInput" accept="image/*" onchange="loadFile(this.files[0])"/>
165
  </div>
166
 
 
167
  <div class="editor" id="editor">
 
168
  <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:10px">
169
  <div class="tab-row">
170
  <button class="tab active" id="t-edit" onclick="sv('edit')">✏️ Edit</button>
 
173
  <button class="act-btn" onclick="resetAll()" style="font-size:12px;padding:7px 14px">← New Image</button>
174
  </div>
175
 
 
176
  <div id="v-edit">
177
  <div class="main-grid">
 
178
  <div style="display:flex;flex-direction:column;gap:14px">
179
  <div class="panel">
180
  <div class="panel-hd">
 
199
  </div>
200
  </div>
201
  </div>
 
202
  <div class="stats-row" id="statsRow" style="display:none">
203
  <div class="stat">📐 <strong id="sDims">—</strong></div>
204
  <div class="stat">⏱ <strong id="sTime">—</strong>ms</div>
205
  <div class="stat">🤖 Model: <strong id="sModel">—</strong></div>
206
+ <div class="stat" style="color:var(--success)" id="sDone">✓ Done!</div>
207
  </div>
208
  </div>
209
 
 
210
  <div class="sidebar">
 
211
  <div class="ctrl-card">
212
  <div class="ctrl-label">🤖 AI Model</div>
213
  <div class="model-grid">
 
234
  </div>
235
  </div>
236
 
 
237
  <div class="ctrl-card">
238
  <div class="ctrl-label">���� Background</div>
239
  <div class="swatch-row">
240
+ <div class="swatch sel" style="background:repeating-conic-gradient(#888 0% 25%,#ccc 0% 50%) 0 0/12px 12px" onclick="selSwatch('transparent',this)" title="Transparent"></div>
241
+ <div class="swatch" style="background:#fff;border-color:#ddd" onclick="selSwatch('#ffffff',this)"></div>
242
+ <div class="swatch" style="background:#000" onclick="selSwatch('#000000',this)"></div>
243
+ <div class="swatch" style="background:#3b82f6" onclick="selSwatch('#3b82f6',this)"></div>
244
+ <div class="swatch" style="background:#22c55e" onclick="selSwatch('#22c55e',this)"></div>
245
+ <div class="swatch" style="background:#f59e0b" onclick="selSwatch('#f59e0b',this)"></div>
246
+ <div class="swatch" style="background:#ec4899" onclick="selSwatch('#ec4899',this)"></div>
247
+ <div class="swatch" style="background:#8b5cf6" onclick="selSwatch('#8b5cf6',this)"></div>
248
  </div>
249
  <div class="color-row">
250
  <input type="color" class="color-picker" id="cpicker" value="#7c5cfc" oninput="selSwatch(this.value,null)"/>
 
252
  </div>
253
  </div>
254
 
 
255
  <div class="ctrl-card">
256
  <div class="ctrl-label">🔧 Edge Refinement</div>
257
  <div class="sl-row">
 
260
  </div>
261
  </div>
262
 
 
263
  <div class="ctrl-card">
264
  <div class="ctrl-label">✨ Effects</div>
265
  <div class="tog-row">
 
278
  </div>
279
  </div>
280
 
 
281
  <div class="ctrl-card">
282
  <div class="ctrl-label">📤 Output</div>
283
  <div style="display:flex;gap:8px;margin-bottom:10px">
 
294
  </div>
295
  </div>
296
 
 
297
  <button class="process-btn" id="procBtn" onclick="processImage()">
298
  <span id="procIcon">✨</span> <span id="procTxt">Remove Background</span>
299
  </button>
 
303
  </div>
304
  </div>
305
 
 
306
  <div class="action-bar">
307
  <button class="act-btn" onclick="copyImg()" id="copyBtn" disabled>📋 Copy PNG</button>
308
  <div style="flex:1"></div>
 
310
  <button class="act-btn dl" onclick="dlResult()" id="dlBtn" disabled>⬇ Download</button>
311
  </div>
312
 
 
313
  <div id="histSection" style="margin-top:24px;display:none">
314
  <div style="font-family:'Syne',sans-serif;font-weight:700;font-size:15px;color:var(--muted);margin-bottom:4px">History <span id="histCount" style="font-weight:400;font-size:13px"></span></div>
315
  <div class="hist-grid" id="histGrid"></div>
316
  </div>
317
  </div>
318
 
 
319
  <div id="v-compare" style="display:none">
320
  <div class="panel">
321
  <div class="panel-hd"><span class="panel-title">⚡ Before / After — drag to compare</span></div>
 
330
  <div id="toast"><span id="tIcon"></span><span id="tMsg"></span></div>
331
 
332
  <script>
333
+ // ── AUTO-DETECT SERVER URL ───────────────────────────────
334
+ // Works on localhost AND on HuggingFace/Railway/any deployment
335
+ function getServer(){
336
+ return window.location.origin;
337
+ }
338
+
339
  let origFile = null, origDataUrl = null;
340
  let resultBlob = null, resultUrl = null;
341
  let selModel = 'general';
 
343
  let history = [];
344
  let curView = 'edit';
345
 
 
 
 
346
  async function testServer(){
347
  const url = getServer();
348
  const dot = document.getElementById('statusDot');
349
  const txt = document.getElementById('statusText');
350
+ dot.className = 'status-dot'; txt.textContent = 'Checking…';
351
  try {
352
+ const r = await fetch(url+'/health', {signal: AbortSignal.timeout(6000)});
353
  const d = await r.json();
354
  dot.className = 'status-dot online';
355
  txt.textContent = 'Server online ✓';
356
+ toast('✓','Connected! Models: '+d.models.join(', '),'success');
357
  } catch(e){
358
  dot.className = 'status-dot offline';
359
  txt.textContent = 'Server offline';
360
+ toast('⚠','Cannot reach server','error');
361
  }
362
  }
363
 
 
364
  function dov(e){ e.preventDefault(); document.getElementById('dropZone').classList.add('over'); }
365
  function dol(){ document.getElementById('dropZone').classList.remove('over'); }
366
  function dod(e){ e.preventDefault(); dol(); const f=e.dataTransfer.files[0]; if(f&&f.type.startsWith('image/')) loadFile(f); }
367
 
 
368
  function loadFile(file){
369
  if(!file) return;
370
  origFile = file;
 
373
  origDataUrl = e.target.result;
374
  document.getElementById('uploadSection').style.display='none';
375
  document.getElementById('editor').classList.add('show');
 
376
  const op = document.getElementById('origPanel');
377
  op.innerHTML = `<img src="${origDataUrl}"/>`;
378
  document.getElementById('imgMeta').textContent = file.name+' · '+(file.size<1048576?(file.size/1024).toFixed(1)+'KB':(file.size/1048576).toFixed(2)+'MB');
 
379
  document.getElementById('resPanel').innerHTML = `<div class="ph" id="resPlaceholder"><div class="ph-icon">✨</div>Click "Remove Background"</div><div class="proc-overlay" id="procOverlay"><div class="spinner"></div><div class="proc-msg">Processing with rembg…</div><div class="proc-sub" id="procSub">Loading model</div></div>`;
380
  resultBlob=null; resultUrl=null;
381
  document.getElementById('dlBtn').disabled=true;
 
391
  document.getElementById('editor').classList.remove('show');
392
  }
393
 
 
394
  async function processImage(){
395
  if(!origFile) return toast('⚠','Upload an image first','error');
396
  const url = getServer();
 
400
  document.getElementById('procIcon').textContent='⏳';
401
  document.getElementById('procTxt').textContent='Sending to rembg…';
402
  overlay.style.display='flex';
403
+ const rp = document.getElementById('resPlaceholder');
404
+ if(rp) rp.style.display='none';
405
 
 
406
  const pw = document.getElementById('progWrap'); pw.style.display='block';
407
  const pb = document.getElementById('progBar');
408
  let prog=0, pInterval = setInterval(()=>{ prog=Math.min(prog+Math.random()*8,88); pb.style.width=prog+'%'; },300);
 
409
  const msgs=['Loading AI model…','Detecting edges…','Separating subject…','Refining mask…','Compositing result…'];
410
  let mi=0, mInterval = setInterval(()=>{ const el=document.getElementById('procSub'); if(el) el.textContent=msgs[mi++%msgs.length]; },1500);
411
 
 
431
 
432
  resultBlob = blob;
433
  resultUrl = URL.createObjectURL(blob);
 
434
  const elapsed = resp.headers.get('X-Processing-Time') || (Date.now()-t0);
435
  const w = resp.headers.get('X-Image-Width')||'—';
436
  const h = resp.headers.get('X-Image-Height')||'—';
 
446
  document.getElementById('sDims').textContent=w+'×'+h;
447
  document.getElementById('sTime').textContent=elapsed;
448
  document.getElementById('sModel').textContent=selModel;
 
449
  document.getElementById('resMeta').textContent=w+'×'+h+' '+document.getElementById('outFmt').value.toUpperCase();
 
450
  document.getElementById('dlBtn').disabled=false;
451
  document.getElementById('copyBtn').disabled=false;
452
  addHistory(resultUrl, origFile.name);
453
  if(curView==='compare') buildCompare();
454
+ toast('✓','Background removed!','success');
455
  } catch(e){
456
  clearInterval(pInterval); clearInterval(mInterval);
457
  pw.style.display='none';
 
465
  document.getElementById('procTxt').textContent='Remove Background';
466
  }
467
 
 
468
  function setModel(m,el){
469
  selModel=m;
470
  document.querySelectorAll('.model-btn').forEach(b=>b.classList.remove('active'));
 
477
  document.getElementById('hexIn').value = c==='transparent'?'':c;
478
  if(c!=='transparent'&&c.startsWith('#')) document.getElementById('cpicker').value=c;
479
  }
480
+ function onHex(v){ if(/^#[0-9a-fA-F]{6}$/.test(v)) selSwatch(v,null); }
 
 
481
  function toggleShadowOpts(on){ document.getElementById('shadowOpts').style.display=on?'block':'none'; }
482
 
 
483
  function dlResult(){
484
  if(!resultBlob) return;
485
  const fmt = document.getElementById('outFmt').value;
486
  const a = document.createElement('a');
487
+ a.href = resultUrl; a.download = 'cutout.'+fmt; a.click();
 
 
488
  toast('⬇','Downloaded!','success');
489
  }
490
 
 
493
  try {
494
  const png = resultBlob.type==='image/png' ? resultBlob : await convertToPng(resultBlob);
495
  await navigator.clipboard.write([new ClipboardItem({'image/png':png})]);
496
+ toast('📋','Copied!','success');
497
  } catch(e){ toast('⚠','Copy failed: '+e.message,'error'); }
498
  }
 
499
  async function convertToPng(blob){
500
  const url=URL.createObjectURL(blob);
501
+ const img=await new Promise((res,rej)=>{ const i=new Image(); i.onload=()=>res(i); i.onerror=rej; i.src=url; });
502
  const c=document.createElement('canvas'); c.width=img.width; c.height=img.height;
503
  c.getContext('2d').drawImage(img,0,0);
504
  return new Promise(res=>c.toBlob(res,'image/png'));
505
  }
 
506
 
 
507
  function sv(v){
508
  curView=v;
509
  ['edit','compare'].forEach(x=>{ document.getElementById('v-'+x).style.display=x===v?'block':'none'; document.getElementById('t-'+x).classList.toggle('active',x===v); });
 
514
  const c=document.getElementById('cmpContainer');
515
  if(!resultUrl||!origDataUrl){ c.innerHTML='<div class="ph"><div class="ph-icon">⚡</div>Process an image first</div>'; return; }
516
  c.innerHTML=`<div class="cmp-wrap" id="cw" style="max-width:640px;width:100%"><img src="${origDataUrl}" id="cb"/><div class="cmp-after" id="ca" style="clip-path:inset(0 50% 0 0)"><img src="${resultUrl}"/></div><div class="cmp-div" id="cd" style="left:50%"><div class="cmp-handle">↔</div></div><div class="cmp-labels"><span class="clabel">Original</span><span class="clabel">Result</span></div></div>`;
517
+ const wrap=document.getElementById('cw'),after=document.getElementById('ca'),div=document.getElementById('cd');
518
  let drag=false;
519
  wrap.addEventListener('mousedown',()=>drag=true);
520
  document.addEventListener('mouseup',()=>drag=false);
521
+ document.addEventListener('mousemove',e=>{ if(!drag)return; const r=wrap.getBoundingClientRect(); const p=Math.max(0,Math.min(100,((e.clientX-r.left)/r.width)*100)); div.style.left=p+'%'; after.style.clipPath=`inset(0 ${100-p}% 0 0)`; });
522
+ wrap.addEventListener('touchmove',e=>{ const r=wrap.getBoundingClientRect(); const p=Math.max(0,Math.min(100,((e.touches[0].clientX-r.left)/r.width)*100)); div.style.left=p+'%'; after.style.clipPath=`inset(0 ${100-p}% 0 0)`; },{passive:true});
 
 
 
 
 
 
 
 
523
  }
524
 
 
525
  function addHistory(url,name){
526
  history.unshift({url,name});
527
  if(history.length>16) history.pop();
 
539
  toast('↩','Restored from history','success');
540
  }
541
 
 
542
  function toast(icon,msg,type=''){
543
  const t=document.getElementById('toast');
544
  document.getElementById('tIcon').textContent=icon;
 
547
  t.classList.add('show'); setTimeout(()=>t.classList.remove('show'),3500);
548
  }
549
 
 
550
  testServer();
551
  </script>
552
  </body>