Spaces:
Running
Running
Tôi muốn có một ứng dụng hoạt động như một chiếc gương thử đồ tại các shop thời trang, ứng dụng này được tích hợp flux kontext để tạo ra hình ảnh realtime của người dùng và các loại sản phẩm họ chọn. Ứng dụng cần được tối ưu hiển thị cho màn hình dọc. Phần gương soi chiếm trọn màn hình. Tổng thể UI tinh tế, UX hoàn hảo - Initial Deployment
Browse files- README.md +7 -5
- index.html +556 -19
README.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: static
|
7 |
pinned: false
|
|
|
|
|
8 |
---
|
9 |
|
10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: fitting-mirror
|
3 |
+
emoji: 🐳
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: yellow
|
6 |
sdk: static
|
7 |
pinned: false
|
8 |
+
tags:
|
9 |
+
- deepsite
|
10 |
---
|
11 |
|
12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
@@ -1,19 +1,556 @@
|
|
1 |
-
<!
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Virtual Dressing Mirror</title>
|
7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
|
9 |
+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix@latest"></script>
|
10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
11 |
+
<style>
|
12 |
+
.mirror-container {
|
13 |
+
position: relative;
|
14 |
+
width: 100%;
|
15 |
+
height: 100vh;
|
16 |
+
overflow: hidden;
|
17 |
+
background-color: #000;
|
18 |
+
}
|
19 |
+
|
20 |
+
#mirror-video {
|
21 |
+
width: 100%;
|
22 |
+
height: 100%;
|
23 |
+
object-fit: cover;
|
24 |
+
transform: scaleX(-1);
|
25 |
+
}
|
26 |
+
|
27 |
+
#mirror-canvas {
|
28 |
+
position: absolute;
|
29 |
+
top: 0;
|
30 |
+
left: 0;
|
31 |
+
width: 100%;
|
32 |
+
height: 100%;
|
33 |
+
z-index: 10;
|
34 |
+
}
|
35 |
+
|
36 |
+
.product-overlay {
|
37 |
+
position: absolute;
|
38 |
+
top: 0;
|
39 |
+
left: 0;
|
40 |
+
width: 100%;
|
41 |
+
height: 100%;
|
42 |
+
z-index: 20;
|
43 |
+
pointer-events: none;
|
44 |
+
}
|
45 |
+
|
46 |
+
.product-item {
|
47 |
+
position: absolute;
|
48 |
+
transition: all 0.3s ease;
|
49 |
+
}
|
50 |
+
|
51 |
+
.controls {
|
52 |
+
position: absolute;
|
53 |
+
bottom: 20px;
|
54 |
+
left: 0;
|
55 |
+
width: 100%;
|
56 |
+
z-index: 30;
|
57 |
+
display: flex;
|
58 |
+
justify-content: center;
|
59 |
+
gap: 15px;
|
60 |
+
}
|
61 |
+
|
62 |
+
.control-btn {
|
63 |
+
width: 60px;
|
64 |
+
height: 60px;
|
65 |
+
border-radius: 50%;
|
66 |
+
background: rgba(255, 255, 255, 0.2);
|
67 |
+
backdrop-filter: blur(10px);
|
68 |
+
display: flex;
|
69 |
+
align-items: center;
|
70 |
+
justify-content: center;
|
71 |
+
color: white;
|
72 |
+
font-size: 24px;
|
73 |
+
cursor: pointer;
|
74 |
+
transition: all 0.2s ease;
|
75 |
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
76 |
+
}
|
77 |
+
|
78 |
+
.control-btn:hover {
|
79 |
+
background: rgba(255, 255, 255, 0.3);
|
80 |
+
transform: scale(1.1);
|
81 |
+
}
|
82 |
+
|
83 |
+
.product-selector {
|
84 |
+
position: absolute;
|
85 |
+
bottom: 100px;
|
86 |
+
left: 0;
|
87 |
+
width: 100%;
|
88 |
+
height: 120px;
|
89 |
+
z-index: 30;
|
90 |
+
display: flex;
|
91 |
+
justify-content: center;
|
92 |
+
gap: 10px;
|
93 |
+
padding: 0 20px;
|
94 |
+
overflow-x: auto;
|
95 |
+
scrollbar-width: none;
|
96 |
+
}
|
97 |
+
|
98 |
+
.product-selector::-webkit-scrollbar {
|
99 |
+
display: none;
|
100 |
+
}
|
101 |
+
|
102 |
+
.product-thumbnail {
|
103 |
+
width: 80px;
|
104 |
+
height: 80px;
|
105 |
+
border-radius: 10px;
|
106 |
+
background: rgba(255, 255, 255, 0.1);
|
107 |
+
backdrop-filter: blur(5px);
|
108 |
+
display: flex;
|
109 |
+
align-items: center;
|
110 |
+
justify-content: center;
|
111 |
+
color: white;
|
112 |
+
cursor: pointer;
|
113 |
+
transition: all 0.2s ease;
|
114 |
+
flex-shrink: 0;
|
115 |
+
overflow: hidden;
|
116 |
+
border: 2px solid rgba(255, 255, 255, 0.2);
|
117 |
+
}
|
118 |
+
|
119 |
+
.product-thumbnail img {
|
120 |
+
width: 100%;
|
121 |
+
height: 100%;
|
122 |
+
object-fit: cover;
|
123 |
+
}
|
124 |
+
|
125 |
+
.product-thumbnail.active {
|
126 |
+
border-color: white;
|
127 |
+
transform: scale(1.1);
|
128 |
+
}
|
129 |
+
|
130 |
+
.loading-overlay {
|
131 |
+
position: absolute;
|
132 |
+
top: 0;
|
133 |
+
left: 0;
|
134 |
+
width: 100%;
|
135 |
+
height: 100%;
|
136 |
+
background: rgba(0, 0, 0, 0.8);
|
137 |
+
z-index: 100;
|
138 |
+
display: flex;
|
139 |
+
flex-direction: column;
|
140 |
+
align-items: center;
|
141 |
+
justify-content: center;
|
142 |
+
color: white;
|
143 |
+
font-size: 18px;
|
144 |
+
}
|
145 |
+
|
146 |
+
.spinner {
|
147 |
+
width: 50px;
|
148 |
+
height: 50px;
|
149 |
+
border: 5px solid rgba(255, 255, 255, 0.3);
|
150 |
+
border-radius: 50%;
|
151 |
+
border-top-color: white;
|
152 |
+
animation: spin 1s ease-in-out infinite;
|
153 |
+
margin-bottom: 20px;
|
154 |
+
}
|
155 |
+
|
156 |
+
@keyframes spin {
|
157 |
+
to { transform: rotate(360deg); }
|
158 |
+
}
|
159 |
+
|
160 |
+
.category-selector {
|
161 |
+
position: absolute;
|
162 |
+
top: 20px;
|
163 |
+
left: 0;
|
164 |
+
width: 100%;
|
165 |
+
z-index: 30;
|
166 |
+
display: flex;
|
167 |
+
justify-content: center;
|
168 |
+
gap: 10px;
|
169 |
+
padding: 10px;
|
170 |
+
background: rgba(0, 0, 0, 0.5);
|
171 |
+
backdrop-filter: blur(5px);
|
172 |
+
}
|
173 |
+
|
174 |
+
.category-btn {
|
175 |
+
padding: 8px 15px;
|
176 |
+
border-radius: 20px;
|
177 |
+
background: rgba(255, 255, 255, 0.1);
|
178 |
+
color: white;
|
179 |
+
font-size: 14px;
|
180 |
+
cursor: pointer;
|
181 |
+
transition: all 0.2s ease;
|
182 |
+
border: none;
|
183 |
+
outline: none;
|
184 |
+
}
|
185 |
+
|
186 |
+
.category-btn.active {
|
187 |
+
background: white;
|
188 |
+
color: black;
|
189 |
+
}
|
190 |
+
|
191 |
+
.product-info {
|
192 |
+
position: absolute;
|
193 |
+
top: 20px;
|
194 |
+
right: 20px;
|
195 |
+
background: rgba(0, 0, 0, 0.7);
|
196 |
+
backdrop-filter: blur(5px);
|
197 |
+
color: white;
|
198 |
+
padding: 15px;
|
199 |
+
border-radius: 10px;
|
200 |
+
max-width: 200px;
|
201 |
+
z-index: 30;
|
202 |
+
display: none;
|
203 |
+
}
|
204 |
+
|
205 |
+
.product-info h3 {
|
206 |
+
margin: 0 0 5px 0;
|
207 |
+
font-size: 16px;
|
208 |
+
}
|
209 |
+
|
210 |
+
.product-info p {
|
211 |
+
margin: 0;
|
212 |
+
font-size: 14px;
|
213 |
+
opacity: 0.8;
|
214 |
+
}
|
215 |
+
|
216 |
+
.product-info .price {
|
217 |
+
font-weight: bold;
|
218 |
+
margin-top: 10px;
|
219 |
+
font-size: 18px;
|
220 |
+
}
|
221 |
+
</style>
|
222 |
+
</head>
|
223 |
+
<body class="bg-black text-white">
|
224 |
+
<div class="mirror-container">
|
225 |
+
<!-- Loading overlay -->
|
226 |
+
<div class="loading-overlay">
|
227 |
+
<div class="spinner"></div>
|
228 |
+
<div>Initializing virtual mirror...</div>
|
229 |
+
</div>
|
230 |
+
|
231 |
+
<!-- Video feed -->
|
232 |
+
<video id="mirror-video" autoplay muted></video>
|
233 |
+
|
234 |
+
<!-- Canvas for segmentation -->
|
235 |
+
<canvas id="mirror-canvas"></canvas>
|
236 |
+
|
237 |
+
<!-- Product overlays -->
|
238 |
+
<div class="product-overlay" id="product-overlay"></div>
|
239 |
+
|
240 |
+
<!-- Category selector -->
|
241 |
+
<div class="category-selector">
|
242 |
+
<button class="category-btn active" data-category="tops">Tops</button>
|
243 |
+
<button class="category-btn" data-category="bottoms">Bottoms</button>
|
244 |
+
<button class="category-btn" data-category="dresses">Dresses</button>
|
245 |
+
<button class="category-btn" data-category="outerwear">Outerwear</button>
|
246 |
+
<button class="category-btn" data-category="accessories">Accessories</button>
|
247 |
+
</div>
|
248 |
+
|
249 |
+
<!-- Product info panel -->
|
250 |
+
<div class="product-info" id="product-info">
|
251 |
+
<h3 id="product-name">Product Name</h3>
|
252 |
+
<p id="product-description">Product description goes here</p>
|
253 |
+
<div class="price" id="product-price">$99.99</div>
|
254 |
+
</div>
|
255 |
+
|
256 |
+
<!-- Product selector -->
|
257 |
+
<div class="product-selector" id="product-selector">
|
258 |
+
<!-- Products will be loaded here -->
|
259 |
+
</div>
|
260 |
+
|
261 |
+
<!-- Controls -->
|
262 |
+
<div class="controls">
|
263 |
+
<div class="control-btn" id="capture-btn">
|
264 |
+
<i class="fas fa-camera"></i>
|
265 |
+
</div>
|
266 |
+
<div class="control-btn" id="toggle-mirror-btn">
|
267 |
+
<i class="fas fa-eye"></i>
|
268 |
+
</div>
|
269 |
+
<div class="control-btn" id="reset-btn">
|
270 |
+
<i class="fas fa-redo"></i>
|
271 |
+
</div>
|
272 |
+
</div>
|
273 |
+
</div>
|
274 |
+
|
275 |
+
<script>
|
276 |
+
// DOM elements
|
277 |
+
const video = document.getElementById('mirror-video');
|
278 |
+
const canvas = document.getElementById('mirror-canvas');
|
279 |
+
const ctx = canvas.getContext('2d');
|
280 |
+
const productOverlay = document.getElementById('product-overlay');
|
281 |
+
const productSelector = document.getElementById('product-selector');
|
282 |
+
const categoryButtons = document.querySelectorAll('.category-btn');
|
283 |
+
const captureBtn = document.getElementById('capture-btn');
|
284 |
+
const toggleMirrorBtn = document.getElementById('toggle-mirror-btn');
|
285 |
+
const resetBtn = document.getElementById('reset-btn');
|
286 |
+
const productInfo = document.getElementById('product-info');
|
287 |
+
const loadingOverlay = document.querySelector('.loading-overlay');
|
288 |
+
|
289 |
+
// App state
|
290 |
+
let net;
|
291 |
+
let currentCategory = 'tops';
|
292 |
+
let selectedProduct = null;
|
293 |
+
let products = {
|
294 |
+
tops: [
|
295 |
+
{ id: 't1', name: 'Classic White Tee', price: '$29.99', description: '100% cotton crew neck t-shirt', image: 'https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 40, rotation: 0 } },
|
296 |
+
{ id: 't2', name: 'Striped Blouse', price: '$49.99', description: 'Silk blend striped blouse', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 40, rotation: 0 } },
|
297 |
+
{ id: 't3', name: 'Black Crop Top', price: '$34.99', description: 'Stretchy black crop top', image: 'https://images.unsplash.com/photo-1576566588028-4147f3842f27?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 40, rotation: 0 } },
|
298 |
+
{ id: 't4', name: 'Denim Shirt', price: '$59.99', description: 'Classic denim button-up', image: 'https://images.unsplash.com/photo-1598033129183-c4f50c736f10?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 40, rotation: 0 } },
|
299 |
+
{ id: 't5', name: 'Pink Sweater', price: '$65.99', description: 'Cozy knit sweater', image: 'https://images.unsplash.com/photo-1520367445093-50dc08a59d9d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 40, rotation: 0 } }
|
300 |
+
],
|
301 |
+
bottoms: [
|
302 |
+
{ id: 'b1', name: 'Skinny Jeans', price: '$79.99', description: 'Stretch denim skinny jeans', image: 'https://images.unsplash.com/photo-1541099649105-f69ad21a3246?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 60, width: 40, rotation: 0 } },
|
303 |
+
{ id: 'b2', name: 'Black Slacks', price: '$89.99', description: 'Tailored work pants', image: 'https://images.unsplash.com/photo-1595950653106-6c9ebd614d3a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 60, width: 40, rotation: 0 } },
|
304 |
+
{ id: 'b3', name: 'Pleated Skirt', price: '$59.99', description: 'Mid-length pleated skirt', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 60, width: 40, rotation: 0 } },
|
305 |
+
{ id: 'b4', name: 'Denim Shorts', price: '$49.99', description: 'High-waisted denim shorts', image: 'https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 60, width: 40, rotation: 0 } },
|
306 |
+
{ id: 'b5', name: 'Yoga Pants', price: '$54.99', description: 'High-performance leggings', image: 'https://images.unsplash.com/photo-1583744949095-cf0f6a4e6c5a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 60, width: 40, rotation: 0 } }
|
307 |
+
],
|
308 |
+
dresses: [
|
309 |
+
{ id: 'd1', name: 'Little Black Dress', price: '$99.99', description: 'Classic A-line dress', image: 'https://images.unsplash.com/photo-1539533018447-63fcce2678e5?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 40, width: 45, rotation: 0 } },
|
310 |
+
{ id: 'd2', name: 'Floral Sundress', price: '$79.99', description: 'Lightweight summer dress', image: 'https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 40, width: 45, rotation: 0 } },
|
311 |
+
{ id: 'd3', name: 'Wrap Dress', price: '$89.99', description: 'Flattering wrap silhouette', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 40, width: 45, rotation: 0 } },
|
312 |
+
{ id: 'd4', name: 'Maxi Dress', price: '$109.99', description: 'Floor-length bohemian dress', image: 'https://images.unsplash.com/photo-1572804013309-59a88b7e92f1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 40, width: 45, rotation: 0 } },
|
313 |
+
{ id: 'd5', name: 'Cocktail Dress', price: '$119.99', description: 'Sequin embellished party dress', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 40, width: 45, rotation: 0 } }
|
314 |
+
],
|
315 |
+
outerwear: [
|
316 |
+
{ id: 'o1', name: 'Denim Jacket', price: '$89.99', description: 'Classic blue denim jacket', image: 'https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 45, rotation: 0 } },
|
317 |
+
{ id: 'o2', name: 'Trench Coat', price: '$149.99', description: 'Water-resistant classic trench', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 45, rotation: 0 } },
|
318 |
+
{ id: 'o3', name: 'Leather Jacket', price: '$199.99', description: 'Genuine leather biker jacket', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 45, rotation: 0 } },
|
319 |
+
{ id: 'o4', name: 'Puffer Jacket', price: '$129.99', description: 'Warm winter puffer', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 45, rotation: 0 } },
|
320 |
+
{ id: 'o5', name: 'Blazer', price: '$119.99', description: 'Tailored work blazer', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 30, width: 45, rotation: 0 } }
|
321 |
+
],
|
322 |
+
accessories: [
|
323 |
+
{ id: 'a1', name: 'Silk Scarf', price: '$39.99', description: 'Printed silk scarf', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 20, width: 30, rotation: 0 } },
|
324 |
+
{ id: 'a2', name: 'Statement Necklace', price: '$59.99', description: 'Chunky gold-tone necklace', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 20, width: 30, rotation: 0 } },
|
325 |
+
{ id: 'a3', name: 'Wide Brim Hat', price: '$49.99', description: 'Straw sun hat', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 10, width: 40, rotation: 0 } },
|
326 |
+
{ id: 'a4', name: 'Designer Handbag', price: '$199.99', description: 'Leather crossbody bag', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 20, y: 60, width: 20, rotation: 0 } },
|
327 |
+
{ id: 'a5', name: 'Oversized Sunglasses', price: '$79.99', description: 'UV-protective lenses', image: 'https://images.unsplash.com/photo-1551232864-3f0890e580d9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200&q=80', overlay: 'https://i.ibb.co/7QpKsCX/image.png', position: { x: 50, y: 25, width: 30, rotation: 0 } }
|
328 |
+
]
|
329 |
+
};
|
330 |
+
|
331 |
+
// Initialize the app
|
332 |
+
async function init() {
|
333 |
+
try {
|
334 |
+
// Load body segmentation model
|
335 |
+
net = await bodyPix.load({
|
336 |
+
architecture: 'MobileNetV1',
|
337 |
+
outputStride: 16,
|
338 |
+
multiplier: 0.75,
|
339 |
+
quantBytes: 2
|
340 |
+
});
|
341 |
+
|
342 |
+
// Start camera
|
343 |
+
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
344 |
+
const stream = await navigator.mediaDevices.getUserMedia({
|
345 |
+
video: {
|
346 |
+
facingMode: 'user',
|
347 |
+
width: { ideal: 1080 },
|
348 |
+
height: { ideal: 1920 }
|
349 |
+
},
|
350 |
+
audio: false
|
351 |
+
});
|
352 |
+
video.srcObject = stream;
|
353 |
+
|
354 |
+
// Wait for video to be ready
|
355 |
+
video.onloadedmetadata = () => {
|
356 |
+
// Set canvas dimensions to match video
|
357 |
+
canvas.width = video.videoWidth;
|
358 |
+
canvas.height = video.videoHeight;
|
359 |
+
|
360 |
+
// Hide loading overlay
|
361 |
+
setTimeout(() => {
|
362 |
+
loadingOverlay.style.display = 'none';
|
363 |
+
}, 1000);
|
364 |
+
|
365 |
+
// Start segmentation loop
|
366 |
+
segmentationLoop();
|
367 |
+
};
|
368 |
+
} else {
|
369 |
+
alert('Camera access is not supported by your browser');
|
370 |
+
}
|
371 |
+
|
372 |
+
// Load initial products
|
373 |
+
loadProducts(currentCategory);
|
374 |
+
|
375 |
+
// Set up event listeners
|
376 |
+
setupEventListeners();
|
377 |
+
} catch (error) {
|
378 |
+
console.error('Error initializing app:', error);
|
379 |
+
loadingOverlay.querySelector('div').textContent = 'Error initializing. Please refresh the page.';
|
380 |
+
}
|
381 |
+
}
|
382 |
+
|
383 |
+
// Set up event listeners
|
384 |
+
function setupEventListeners() {
|
385 |
+
// Category buttons
|
386 |
+
categoryButtons.forEach(btn => {
|
387 |
+
btn.addEventListener('click', () => {
|
388 |
+
// Update active button
|
389 |
+
categoryButtons.forEach(b => b.classList.remove('active'));
|
390 |
+
btn.classList.add('active');
|
391 |
+
|
392 |
+
// Load new category
|
393 |
+
currentCategory = btn.dataset.category;
|
394 |
+
loadProducts(currentCategory);
|
395 |
+
});
|
396 |
+
});
|
397 |
+
|
398 |
+
// Capture button
|
399 |
+
captureBtn.addEventListener('click', captureOutfit);
|
400 |
+
|
401 |
+
// Toggle mirror button
|
402 |
+
toggleMirrorBtn.addEventListener('click', toggleMirrorEffect);
|
403 |
+
|
404 |
+
// Reset button
|
405 |
+
resetBtn.addEventListener('click', resetProducts);
|
406 |
+
}
|
407 |
+
|
408 |
+
// Load products for a category
|
409 |
+
function loadProducts(category) {
|
410 |
+
productSelector.innerHTML = '';
|
411 |
+
|
412 |
+
products[category].forEach(product => {
|
413 |
+
const productEl = document.createElement('div');
|
414 |
+
productEl.className = 'product-thumbnail';
|
415 |
+
productEl.dataset.id = product.id;
|
416 |
+
|
417 |
+
const img = document.createElement('img');
|
418 |
+
img.src = product.image;
|
419 |
+
img.alt = product.name;
|
420 |
+
|
421 |
+
productEl.appendChild(img);
|
422 |
+
|
423 |
+
productEl.addEventListener('click', () => {
|
424 |
+
// Highlight selected product
|
425 |
+
document.querySelectorAll('.product-thumbnail').forEach(el => {
|
426 |
+
el.classList.remove('active');
|
427 |
+
});
|
428 |
+
productEl.classList.add('active');
|
429 |
+
|
430 |
+
// Show product on mirror
|
431 |
+
showProduct(product);
|
432 |
+
});
|
433 |
+
|
434 |
+
productSelector.appendChild(productEl);
|
435 |
+
});
|
436 |
+
}
|
437 |
+
|
438 |
+
// Show product on mirror
|
439 |
+
function showProduct(product) {
|
440 |
+
selectedProduct = product;
|
441 |
+
|
442 |
+
// Clear previous products
|
443 |
+
productOverlay.innerHTML = '';
|
444 |
+
|
445 |
+
// Create product element
|
446 |
+
const productEl = document.createElement('div');
|
447 |
+
productEl.className = 'product-item';
|
448 |
+
productEl.dataset.id = product.id;
|
449 |
+
|
450 |
+
const img = document.createElement('img');
|
451 |
+
img.src = product.overlay;
|
452 |
+
img.alt = product.name;
|
453 |
+
img.style.width = `${product.position.width}%`;
|
454 |
+
img.style.transform = `rotate(${product.position.rotation}deg)`;
|
455 |
+
|
456 |
+
productEl.appendChild(img);
|
457 |
+
productEl.style.left = `${product.position.x}%`;
|
458 |
+
productEl.style.top = `${product.position.y}%`;
|
459 |
+
|
460 |
+
productOverlay.appendChild(productEl);
|
461 |
+
|
462 |
+
// Show product info
|
463 |
+
document.getElementById('product-name').textContent = product.name;
|
464 |
+
document.getElementById('product-description').textContent = product.description;
|
465 |
+
document.getElementById('product-price').textContent = product.price;
|
466 |
+
productInfo.style.display = 'block';
|
467 |
+
}
|
468 |
+
|
469 |
+
// Segmentation loop
|
470 |
+
async function segmentationLoop() {
|
471 |
+
// Perform segmentation
|
472 |
+
const segmentation = await net.segmentPerson(video, {
|
473 |
+
flipHorizontal: true,
|
474 |
+
internalResolution: 'medium',
|
475 |
+
segmentationThreshold: 0.7
|
476 |
+
});
|
477 |
+
|
478 |
+
// Draw segmentation mask
|
479 |
+
const mask = bodyPix.toMask(segmentation);
|
480 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
481 |
+
ctx.putImageData(mask, 0, 0);
|
482 |
+
|
483 |
+
// Continue loop
|
484 |
+
requestAnimationFrame(segmentationLoop);
|
485 |
+
}
|
486 |
+
|
487 |
+
// Capture outfit
|
488 |
+
function captureOutfit() {
|
489 |
+
// Create temporary canvas
|
490 |
+
const tempCanvas = document.createElement('canvas');
|
491 |
+
tempCanvas.width = canvas.width;
|
492 |
+
tempCanvas.height = canvas.height;
|
493 |
+
const tempCtx = tempCanvas.getContext('2d');
|
494 |
+
|
495 |
+
// Draw video frame
|
496 |
+
tempCtx.save();
|
497 |
+
tempCtx.scale(-1, 1);
|
498 |
+
tempCtx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height);
|
499 |
+
tempCtx.restore();
|
500 |
+
|
501 |
+
// Draw products
|
502 |
+
const productElements = document.querySelectorAll('.product-item');
|
503 |
+
productElements.forEach(el => {
|
504 |
+
const img = el.querySelector('img');
|
505 |
+
const rect = el.getBoundingClientRect();
|
506 |
+
|
507 |
+
tempCtx.save();
|
508 |
+
tempCtx.translate(rect.left + rect.width/2, rect.top + rect.height/2);
|
509 |
+
tempCtx.rotate((img.style.transform.match(/rotate\((\d+)deg\)/) || [0,0])[1] * Math.PI / 180);
|
510 |
+
tempCtx.drawImage(img, -rect.width/2, -rect.height/2, rect.width, rect.height);
|
511 |
+
tempCtx.restore();
|
512 |
+
});
|
513 |
+
|
514 |
+
// Create download link
|
515 |
+
const link = document.createElement('a');
|
516 |
+
link.download = 'virtual-mirror-outfit.png';
|
517 |
+
link.href = tempCanvas.toDataURL('image/png');
|
518 |
+
link.click();
|
519 |
+
|
520 |
+
// Show confirmation
|
521 |
+
const confirmation = document.createElement('div');
|
522 |
+
confirmation.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-black bg-opacity-70 text-white px-6 py-3 rounded-lg z-50';
|
523 |
+
confirmation.textContent = 'Outfit captured!';
|
524 |
+
document.body.appendChild(confirmation);
|
525 |
+
|
526 |
+
setTimeout(() => {
|
527 |
+
document.body.removeChild(confirmation);
|
528 |
+
}, 2000);
|
529 |
+
}
|
530 |
+
|
531 |
+
// Toggle mirror effect
|
532 |
+
function toggleMirrorEffect() {
|
533 |
+
if (canvas.style.opacity === '0') {
|
534 |
+
canvas.style.opacity = '1';
|
535 |
+
toggleMirrorBtn.innerHTML = '<i class="fas fa-eye"></i>';
|
536 |
+
} else {
|
537 |
+
canvas.style.opacity = '0';
|
538 |
+
toggleMirrorBtn.innerHTML = '<i class="fas fa-eye-slash"></i>';
|
539 |
+
}
|
540 |
+
}
|
541 |
+
|
542 |
+
// Reset all products
|
543 |
+
function resetProducts() {
|
544 |
+
productOverlay.innerHTML = '';
|
545 |
+
productInfo.style.display = 'none';
|
546 |
+
document.querySelectorAll('.product-thumbnail').forEach(el => {
|
547 |
+
el.classList.remove('active');
|
548 |
+
});
|
549 |
+
selectedProduct = null;
|
550 |
+
}
|
551 |
+
|
552 |
+
// Initialize when DOM is loaded
|
553 |
+
document.addEventListener('DOMContentLoaded', init);
|
554 |
+
</script>
|
555 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=TDN-M/fitting-mirror" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
556 |
+
</html>
|