Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Virtual Dressing Mirror</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix@latest"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.mirror-container { | |
position: relative; | |
width: 100%; | |
height: 100vh; | |
overflow: hidden; | |
background-color: #000; | |
} | |
#mirror-video { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
transform: scaleX(-1); | |
} | |
#mirror-canvas { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 10; | |
} | |
.product-overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
z-index: 20; | |
pointer-events: none; | |
} | |
.product-item { | |
position: absolute; | |
transition: all 0.3s ease; | |
pointer-events: none; | |
} | |
.product-item img { | |
width: 100%; | |
height: 100%; | |
object-fit: contain; | |
} | |
.controls { | |
position: absolute; | |
bottom: 20px; | |
left: 0; | |
width: 100%; | |
z-index: 30; | |
display: flex; | |
justify-content: center; | |
gap: 15px; | |
} | |
.control-btn { | |
width: 60px; | |
height: 60px; | |
border-radius: 50%; | |
background: rgba(255, 255, 255, 0.2); | |
backdrop-filter: blur(10px); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
font-size: 24px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
border: 2px solid rgba(255, 255, 255, 0.3); | |
} | |
.control-btn:hover { | |
background: rgba(255, 255, 255, 0.3); | |
transform: scale(1.1); | |
} | |
.product-selector { | |
position: absolute; | |
bottom: 100px; | |
left: 0; | |
width: 100%; | |
height: 120px; | |
z-index: 30; | |
display: flex; | |
justify-content: center; | |
gap: 10px; | |
padding: 0 20px; | |
overflow-x: auto; | |
scrollbar-width: none; | |
} | |
.product-selector::-webkit-scrollbar { | |
display: none; | |
} | |
.product-thumbnail { | |
width: 80px; | |
height: 80px; | |
border-radius: 10px; | |
background: rgba(255, 255, 255, 0.1); | |
backdrop-filter: blur(5px); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
flex-shrink: 0; | |
overflow: hidden; | |
border: 2px solid rgba(255, 255, 255, 0.2); | |
} | |
.product-thumbnail img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.product-thumbnail.active { | |
border-color: white; | |
transform: scale(1.1); | |
} | |
.loading-overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: rgba(0, 0, 0, 0.8); | |
z-index: 100; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
font-size: 18px; | |
} | |
.spinner { | |
width: 50px; | |
height: 50px; | |
border: 5px solid rgba(255, 255, 255, 0.3); | |
border-radius: 50%; | |
border-top-color: white; | |
animation: spin 1s ease-in-out infinite; | |
margin-bottom: 20px; | |
} | |
@keyframes spin { | |
to { transform: rotate(360deg); } | |
} | |
.category-selector { | |
position: absolute; | |
top: 20px; | |
left: 0; | |
width: 100%; | |
z-index: 30; | |
display: flex; | |
justify-content: center; | |
gap: 10px; | |
padding: 10px; | |
background: rgba(0, 0, 0, 0.5); | |
backdrop-filter: blur(5px); | |
} | |
.category-btn { | |
padding: 8px 15px; | |
border-radius: 20px; | |
background: rgba(255, 255, 255, 0.1); | |
color: white; | |
font-size: 14px; | |
cursor: pointer; | |
transition: all 0.2s ease; | |
border: none; | |
outline: none; | |
} | |
.category-btn.active { | |
background: white; | |
color: black; | |
} | |
.product-info { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
background: rgba(0, 0, 0, 0.7); | |
backdrop-filter: blur(5px); | |
color: white; | |
padding: 15px; | |
border-radius: 10px; | |
max-width: 200px; | |
z-index: 30; | |
display: none; | |
} | |
.product-info h3 { | |
margin: 0 0 5px 0; | |
font-size: 16px; | |
} | |
.product-info p { | |
margin: 0; | |
font-size: 14px; | |
opacity: 0.8; | |
} | |
.product-info .price { | |
font-weight: bold; | |
margin-top: 10px; | |
font-size: 18px; | |
} | |
</style> | |
</head> | |
<body class="bg-black text-white"> | |
<div class="mirror-container"> | |
<!-- Loading overlay --> | |
<div class="loading-overlay"> | |
<div class="spinner"></div> | |
<div>Initializing virtual mirror...</div> | |
</div> | |
<!-- Video feed --> | |
<video id="mirror-video" autoplay muted></video> | |
<!-- Canvas for segmentation --> | |
<canvas id="mirror-canvas"></canvas> | |
<!-- Product overlays --> | |
<div class="product-overlay" id="product-overlay"></div> | |
<!-- Category selector --> | |
<div class="category-selector"> | |
<button class="category-btn active" data-category="tops">Tops</button> | |
<button class="category-btn" data-category="bottoms">Bottoms</button> | |
<button class="category-btn" data-category="dresses">Dresses</button> | |
<button class="category-btn" data-category="outerwear">Outerwear</button> | |
<button class="category-btn" data-category="accessories">Accessories</button> | |
</div> | |
<!-- Product info panel --> | |
<div class="product-info" id="product-info"> | |
<h3 id="product-name">Product Name</h3> | |
<p id="product-description">Product description goes here</p> | |
<div class="price" id="product-price">$99.99</div> | |
</div> | |
<!-- Product selector --> | |
<div class="product-selector" id="product-selector"> | |
<!-- Products will be loaded here --> | |
</div> | |
<!-- Controls --> | |
<div class="controls"> | |
<div class="control-btn" id="capture-btn"> | |
<i class="fas fa-camera"></i> | |
</div> | |
<div class="control-btn" id="toggle-mirror-btn"> | |
<i class="fas fa-eye"></i> | |
</div> | |
<div class="control-btn" id="reset-btn"> | |
<i class="fas fa-redo"></i> | |
</div> | |
</div> | |
</div> | |
<script> | |
// DOM elements | |
const video = document.getElementById('mirror-video'); | |
const canvas = document.getElementById('mirror-canvas'); | |
const ctx = canvas.getContext('2d'); | |
const productOverlay = document.getElementById('product-overlay'); | |
const productSelector = document.getElementById('product-selector'); | |
const categoryButtons = document.querySelectorAll('.category-btn'); | |
const captureBtn = document.getElementById('capture-btn'); | |
const toggleMirrorBtn = document.getElementById('toggle-mirror-btn'); | |
const resetBtn = document.getElementById('reset-btn'); | |
const productInfo = document.getElementById('product-info'); | |
const loadingOverlay = document.querySelector('.loading-overlay'); | |
// App state | |
let net; | |
let currentCategory = 'tops'; | |
let selectedProduct = null; | |
let products = { | |
tops: [ | |
{ 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/LzVsz08/tshirt.png', position: { x: 50, y: 30, width: 40, rotation: 0 } }, | |
{ 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/LzVsz08/tshirt.png', position: { x: 50, y: 30, width: 40, rotation: 0 } }, | |
{ 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/LzVsz08/tshirt.png', position: { x: 50, y: 30, width: 40, rotation: 0 } }, | |
{ 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/LzVsz08/tshirt.png', position: { x: 50, y: 30, width: 40, rotation: 0 } }, | |
{ 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/LzVsz08/tshirt.png', position: { x: 50, y: 30, width: 40, rotation: 0 } } | |
], | |
bottoms: [ | |
{ 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/0Xz9Fk1/jeans.png', position: { x: 50, y: 60, width: 40, rotation: 0 } }, | |
{ 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/0Xz9Fk1/jeans.png', position: { x: 50, y: 60, width: 40, rotation: 0 } }, | |
{ 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/0Xz9Fk1/jeans.png', position: { x: 50, y: 60, width: 40, rotation: 0 } }, | |
{ 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/0Xz9Fk1/jeans.png', position: { x: 50, y: 60, width: 40, rotation: 0 } }, | |
{ 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/0Xz9Fk1/jeans.png', position: { x: 50, y: 60, width: 40, rotation: 0 } } | |
], | |
dresses: [ | |
{ 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/0Xz9Fk1/dress.png', position: { x: 50, y: 40, width: 45, rotation: 0 } }, | |
{ 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/0Xz9Fk1/dress.png', position: { x: 50, y: 40, width: 45, rotation: 0 } }, | |
{ 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/0Xz9Fk1/dress.png', position: { x: 50, y: 40, width: 45, rotation: 0 } }, | |
{ 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/0Xz9Fk1/dress.png', position: { x: 50, y: 40, width: 45, rotation: 0 } }, | |
{ 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/0Xz9Fk1/dress.png', position: { x: 50, y: 40, width: 45, rotation: 0 } } | |
], | |
outerwear: [ | |
{ 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/LzVsz08/jacket.png', position: { x: 50, y: 30, width: 45, rotation: 0 } }, | |
{ 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/LzVsz08/jacket.png', position: { x: 50, y: 30, width: 45, rotation: 0 } }, | |
{ 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/LzVsz08/jacket.png', position: { x: 50, y: 30, width: 45, rotation: 0 } }, | |
{ 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/LzVsz08/jacket.png', position: { x: 50, y: 30, width: 45, rotation: 0 } }, | |
{ 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/LzVsz08/jacket.png', position: { x: 50, y: 30, width: 45, rotation: 0 } } | |
], | |
accessories: [ | |
{ 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/0Xz9Fk1/scarf.png', position: { x: 50, y: 20, width: 30, rotation: 0 } }, | |
{ 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/0Xz9Fk1/necklace.png', position: { x: 50, y: 20, width: 30, rotation: 0 } }, | |
{ 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/LzVsz08/hat.png', position: { x: 50, y: 10, width: 40, rotation: 0 } }, | |
{ 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/0Xz9Fk1/bag.png', position: { x: 20, y: 60, width: 20, rotation: 0 } }, | |
{ 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/LzVsz08/sunglasses.png', position: { x: 50, y: 25, width: 30, rotation: 0 } } | |
] | |
}; | |
// Initialize the app | |
async function init() { | |
try { | |
// Load body segmentation model | |
net = await bodyPix.load({ | |
architecture: 'MobileNetV1', | |
outputStride: 16, | |
multiplier: 0.75, | |
quantBytes: 2 | |
}); | |
// Start camera | |
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
const stream = await navigator.mediaDevices.getUserMedia({ | |
video: { | |
facingMode: 'user', | |
width: { ideal: 1080 }, | |
height: { ideal: 1920 } | |
}, | |
audio: false | |
}); | |
video.srcObject = stream; | |
// Wait for video to be ready | |
video.onloadedmetadata = () => { | |
// Set canvas dimensions to match video | |
canvas.width = video.videoWidth; | |
canvas.height = video.videoHeight; | |
// Hide loading overlay | |
setTimeout(() => { | |
loadingOverlay.style.display = 'none'; | |
}, 1000); | |
// Start segmentation loop | |
segmentationLoop(); | |
}; | |
} else { | |
alert('Camera access is not supported by your browser'); | |
} | |
// Load initial products | |
loadProducts(currentCategory); | |
// Set up event listeners | |
setupEventListeners(); | |
} catch (error) { | |
console.error('Error initializing app:', error); | |
loadingOverlay.querySelector('div').textContent = 'Error initializing. Please refresh the page.'; | |
} | |
} | |
// Set up event listeners | |
function setupEventListeners() { | |
// Category buttons | |
categoryButtons.forEach(btn => { | |
btn.addEventListener('click', () => { | |
// Update active button | |
categoryButtons.forEach(b => b.classList.remove('active')); | |
btn.classList.add('active'); | |
// Load new category | |
currentCategory = btn.dataset.category; | |
loadProducts(currentCategory); | |
}); | |
}); | |
// Capture button | |
captureBtn.addEventListener('click', captureOutfit); | |
// Toggle mirror button | |
toggleMirrorBtn.addEventListener('click', toggleMirrorEffect); | |
// Reset button | |
resetBtn.addEventListener('click', resetProducts); | |
} | |
// Load products for a category | |
function loadProducts(category) { | |
productSelector.innerHTML = ''; | |
products[category].forEach(product => { | |
const productEl = document.createElement('div'); | |
productEl.className = 'product-thumbnail'; | |
productEl.dataset.id = product.id; | |
const img = document.createElement('img'); | |
img.src = product.image; | |
img.alt = product.name; | |
productEl.appendChild(img); | |
productEl.addEventListener('click', () => { | |
// Highlight selected product | |
document.querySelectorAll('.product-thumbnail').forEach(el => { | |
el.classList.remove('active'); | |
}); | |
productEl.classList.add('active'); | |
// Show product on mirror | |
showProduct(product); | |
}); | |
productSelector.appendChild(productEl); | |
}); | |
} | |
// Show product on mirror | |
function showProduct(product) { | |
selectedProduct = product; | |
// Clear previous products of the same category | |
const existingProducts = document.querySelectorAll(`.product-item[data-category="${currentCategory}"]`); | |
existingProducts.forEach(el => el.remove()); | |
// Create product element | |
const productEl = document.createElement('div'); | |
productEl.className = 'product-item'; | |
productEl.dataset.id = product.id; | |
productEl.dataset.category = currentCategory; | |
const img = document.createElement('img'); | |
img.src = product.overlay; | |
img.alt = product.name; | |
productEl.appendChild(img); | |
productEl.style.width = `${product.position.width}%`; | |
productEl.style.left = `${product.position.x}%`; | |
productEl.style.top = `${product.position.y}%`; | |
productEl.style.transform = `translate(-50%, -50%) rotate(${product.position.rotation}deg)`; | |
productOverlay.appendChild(productEl); | |
// Show product info | |
document.getElementById('product-name').textContent = product.name; | |
document.getElementById('product-description').textContent = product.description; | |
document.getElementById('product-price').textContent = product.price; | |
productInfo.style.display = 'block'; | |
} | |
// Segmentation loop | |
async function segmentationLoop() { | |
// Perform segmentation | |
const segmentation = await net.segmentPerson(video, { | |
flipHorizontal: true, | |
internalResolution: 'medium', | |
segmentationThreshold: 0.7 | |
}); | |
// Draw segmentation mask | |
const mask = bodyPix.toMask(segmentation); | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
ctx.putImageData(mask, 0, 0); | |
// Continue loop | |
requestAnimationFrame(segmentationLoop); | |
} | |
// Capture outfit | |
function captureOutfit() { | |
// Create temporary canvas | |
const tempCanvas = document.createElement('canvas'); | |
tempCanvas.width = canvas.width; | |
tempCanvas.height = canvas.height; | |
const tempCtx = tempCanvas.getContext('2d'); | |
// Draw video frame | |
tempCtx.save(); | |
tempCtx.scale(-1, 1); | |
tempCtx.drawImage(video, -canvas.width, 0, canvas.width, canvas.height); | |
tempCtx.restore(); | |
// Draw products | |
const productElements = document.querySelectorAll('.product-item'); | |
productElements.forEach(el => { | |
const img = el.querySelector('img'); | |
const rect = el.getBoundingClientRect(); | |
tempCtx.save(); | |
tempCtx.translate(rect.left + rect.width/2, rect.top + rect.height/2); | |
tempCtx.rotate((img.style.transform.match(/rotate\((\d+)deg\)/) || [0,0])[1] * Math.PI / 180); | |
tempCtx.drawImage(img, -rect.width/2, -rect.height/2, rect.width, rect.height); | |
tempCtx.restore(); | |
}); | |
// Create download link | |
const link = document.createElement('a'); | |
link.download = 'virtual-mirror-outfit.png'; | |
link.href = tempCanvas.toDataURL('image/png'); | |
link.click(); | |
// Show confirmation | |
const confirmation = document.createElement('div'); | |
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'; | |
confirmation.textContent = 'Outfit captured!'; | |
document.body.appendChild(confirmation); | |
setTimeout(() => { | |
document.body.removeChild(confirmation); | |
}, 2000); | |
} | |
// Toggle mirror effect | |
function toggleMirrorEffect() { | |
if (canvas.style.opacity === '0') { | |
canvas.style.opacity = '1'; | |
toggleMirrorBtn.innerHTML = '<i class="fas fa-eye"></i>'; | |
} else { | |
canvas.style.opacity = '0'; | |
toggleMirrorBtn.innerHTML = '<i class="fas fa-eye-slash"></i>'; | |
} | |
} | |
// Reset all products | |
function resetProducts() { | |
productOverlay.innerHTML = ''; | |
productInfo.style.display = 'none'; | |
document.querySelectorAll('.product-thumbnail').forEach(el => { | |
el.classList.remove('active'); | |
}); | |
selectedProduct = null; | |
} | |
// Initialize when DOM is loaded | |
document.addEventListener('DOMContentLoaded', init); | |
</script> | |
<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> | |
</html> |