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> |