Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Virtual Watch Try-On</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- MediaPipe CDN --> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script> | |
| <style> | |
| .watch-container { | |
| position: relative; | |
| width: 100%; | |
| height: 400px; | |
| margin: 0 auto; | |
| overflow: hidden; | |
| } | |
| .camera-feed { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| border-radius: 15px; | |
| display: none; | |
| } | |
| .watch-overlay { | |
| position: absolute; | |
| pointer-events: none; | |
| transition: all 0.3s ease; | |
| } | |
| .color-option { | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| border: 2px solid transparent; | |
| } | |
| .color-option:hover { | |
| transform: scale(1.1); | |
| } | |
| .color-option.selected { | |
| border: 2px solid #000; | |
| transform: scale(1.1); | |
| } | |
| .watch-preview { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-radius: 20px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| position: relative; | |
| } | |
| .loading-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(255,255,255,0.8); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 10; | |
| border-radius: 20px; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid rgba(0, 0, 0, 0.1); | |
| border-radius: 50%; | |
| border-left-color: #3b82f6; | |
| animation: spin 1s linear infinite; | |
| margin-bottom: 10px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .mirror { | |
| transform: scaleX(-1); | |
| } | |
| .model-option { | |
| transition: all 0.2s ease; | |
| } | |
| .model-option:hover { | |
| transform: translateY(-5px); | |
| } | |
| .model-option.selected { | |
| border: 2px solid #3b82f6; | |
| box-shadow: 0 5px 15px rgba(59, 130, 246, 0.3); | |
| } | |
| .band-option { | |
| transition: all 0.2s ease; | |
| } | |
| .band-option:hover { | |
| transform: translateY(-3px); | |
| } | |
| .band-option.selected { | |
| border: 2px solid #3b82f6; | |
| box-shadow: 0 3px 10px rgba(59, 130, 246, 0.2); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Advanced Virtual Watch Try-On</h1> | |
| <p class="text-gray-600">Experience precise watch placement with AI-powered hand tracking</p> | |
| </header> | |
| <div class="flex flex-col lg:flex-row gap-8 items-center justify-center"> | |
| <!-- Watch Preview Section --> | |
| <div class="watch-preview p-8 w-full lg:w-1/2 max-w-lg"> | |
| <div class="loading-overlay" id="loading-overlay"> | |
| <div class="spinner"></div> | |
| <p class="text-gray-700 font-medium">Loading hand tracking model...</p> | |
| </div> | |
| <div class="watch-container relative"> | |
| <!-- Camera feed will be shown here when active --> | |
| <video id="camera-feed" class="camera-feed mirror" autoplay playsinline></video> | |
| <!-- Canvas for MediaPipe hand tracking --> | |
| <canvas id="output-canvas" class="absolute top-0 left-0 w-full h-full" style="display: none;"></canvas> | |
| <!-- Watch overlay that will be positioned on the wrist --> | |
| <img id="watch-overlay" src="https://i.imgur.com/JQZ1lzE.png" alt="Watch" class="watch-overlay" style="display: none;"> | |
| </div> | |
| <div class="flex justify-center mt-6 gap-2"> | |
| <button id="try-on-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-full font-medium transition flex items-center"> | |
| <i class="fas fa-camera mr-2"></i> Live Try-On | |
| </button> | |
| <button id="static-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-2 rounded-full font-medium transition flex items-center"> | |
| <i class="fas fa-image mr-2"></i> Sample Wrist | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Watch Customization Section --> | |
| <div class="w-full lg:w-1/2 max-w-lg"> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Customize Your Watch</h2> | |
| <!-- Model Selection --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-3">Select Model</h3> | |
| <div class="grid grid-cols-3 gap-3"> | |
| <div class="model-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition selected" data-model="1"> | |
| <img src="https://i.imgur.com/JQZ1lzE.png" alt="Classic" class="w-full h-20 object-contain"> | |
| <p class="text-center mt-1 text-sm">Classic</p> | |
| </div> | |
| <div class="model-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition" data-model="2"> | |
| <img src="https://i.imgur.com/mXJQZ9L.png" alt="Sport" class="w-full h-20 object-contain"> | |
| <p class="text-center mt-1 text-sm">Sport</p> | |
| </div> | |
| <div class="model-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition" data-model="3"> | |
| <img src="https://i.imgur.com/nXJQZ9L.png" alt="Luxury" class="w-full h-20 object-contain"> | |
| <p class="text-center mt-1 text-sm">Luxury</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Color Selection --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-3">Select Color</h3> | |
| <div class="flex gap-3"> | |
| <div class="color-option bg-black selected" data-color="black" title="Black"></div> | |
| <div class="color-option bg-slate-500" data-color="silver" title="Silver"></div> | |
| <div class="color-option bg-amber-700" data-color="gold" title="Gold"></div> | |
| <div class="color-option bg-blue-600" data-color="blue" title="Blue"></div> | |
| <div class="color-option bg-red-600" data-color="red" title="Red"></div> | |
| </div> | |
| </div> | |
| <!-- Band Selection --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-3">Select Band</h3> | |
| <div class="flex gap-3"> | |
| <div class="band-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition selected" data-band="leather"> | |
| <img src="https://i.imgur.com/leather-band.png" alt="Leather" class="w-12 h-12 object-contain"> | |
| <p class="text-center mt-1 text-sm">Leather</p> | |
| </div> | |
| <div class="band-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition" data-band="metal"> | |
| <img src="https://i.imgur.com/metal-band.png" alt="Metal" class="w-12 h-12 object-contain"> | |
| <p class="text-center mt-1 text-sm">Metal</p> | |
| </div> | |
| <div class="band-option border rounded-lg p-2 cursor-pointer hover:bg-gray-50 transition" data-band="silicone"> | |
| <img src="https://i.imgur.com/silicone-band.png" alt="Silicone" class="w-12 h-12 object-contain"> | |
| <p class="text-center mt-1 text-sm">Silicone</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Size Adjustment --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-3">Adjust Size</h3> | |
| <div class="flex items-center gap-4"> | |
| <button id="size-down" class="bg-gray-200 hover:bg-gray-300 text-gray-800 w-8 h-8 rounded-full flex items-center justify-center transition"> | |
| <i class="fas fa-minus"></i> | |
| </button> | |
| <div class="flex-1"> | |
| <input type="range" id="size-slider" min="70" max="130" value="100" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| </div> | |
| <button id="size-up" class="bg-gray-200 hover:bg-gray-300 text-gray-800 w-8 h-8 rounded-full flex items-center justify-center transition"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Action Buttons --> | |
| <div class="flex gap-3"> | |
| <button class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium flex-1 transition flex items-center justify-center"> | |
| <i class="fas fa-shopping-cart mr-2"></i> Add to Cart | |
| </button> | |
| <button class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-3 rounded-lg font-medium transition flex items-center justify-center"> | |
| <i class="fas fa-heart mr-2"></i> Save | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Instructions Modal --> | |
| <div id="instructions-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-xl p-6 max-w-md mx-4"> | |
| <h3 class="text-xl font-bold text-gray-800 mb-4">How to Use Virtual Try-On</h3> | |
| <ul class="space-y-3 text-gray-700 mb-6"> | |
| <li class="flex items-start"> | |
| <i class="fas fa-hand-paper text-blue-500 mt-1 mr-2"></i> | |
| <span>Hold your hand in front of your camera with your palm facing the camera</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-lightbulb text-blue-500 mt-1 mr-2"></i> | |
| <span>Make sure you're in a well-lit environment</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-expand text-blue-500 mt-1 mr-2"></i> | |
| <span>Keep your hand at a reasonable distance (not too close or far)</span> | |
| </li> | |
| <li class="flex items-start"> | |
| <i class="fas fa-stopwatch text-blue-500 mt-1 mr-2"></i> | |
| <span>It may take a moment to detect your hand initially</span> | |
| </li> | |
| </ul> | |
| <button id="close-instructions" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg font-medium transition"> | |
| Got it! | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elements | |
| const cameraFeed = document.getElementById('camera-feed'); | |
| const outputCanvas = document.getElementById('output-canvas'); | |
| const watchOverlay = document.getElementById('watch-overlay'); | |
| const tryOnBtn = document.getElementById('try-on-btn'); | |
| const staticBtn = document.getElementById('static-btn'); | |
| const colorOptions = document.querySelectorAll('.color-option'); | |
| const modelOptions = document.querySelectorAll('.model-option'); | |
| const bandOptions = document.querySelectorAll('.band-option'); | |
| const sizeSlider = document.getElementById('size-slider'); | |
| const sizeDown = document.getElementById('size-down'); | |
| const sizeUp = document.getElementById('size-up'); | |
| const loadingOverlay = document.getElementById('loading-overlay'); | |
| const instructionsModal = document.getElementById('instructions-modal'); | |
| const closeInstructions = document.getElementById('close-instructions'); | |
| // Model images (placeholder URLs - replace with actual images) | |
| const models = { | |
| 1: { | |
| image: 'https://i.imgur.com/JQZ1lzE.png', | |
| name: 'Classic', | |
| width: 150, | |
| height: 150, | |
| wristRatio: 0.6 // Ratio of watch width to wrist width | |
| }, | |
| 2: { | |
| image: 'https://i.imgur.com/mXJQZ9L.png', | |
| name: 'Sport', | |
| width: 140, | |
| height: 140, | |
| wristRatio: 0.55 | |
| }, | |
| 3: { | |
| image: 'https://i.imgur.com/nXJQZ9L.png', | |
| name: 'Luxury', | |
| width: 160, | |
| height: 160, | |
| wristRatio: 0.65 | |
| } | |
| }; | |
| // Color filters (simplified - in a real app you'd use different images) | |
| const colorFilters = { | |
| black: 'brightness(0) saturate(100%)', | |
| silver: 'brightness(1.5) saturate(0.8) hue-rotate(-10deg)', | |
| gold: 'brightness(1.2) saturate(1.5) hue-rotate(10deg)', | |
| blue: 'brightness(0.8) saturate(1.5) hue-rotate(200deg)', | |
| red: 'brightness(0.9) saturate(1.8) hue-rotate(350deg)' | |
| }; | |
| // Current state | |
| let currentMode = 'static'; // 'camera' or 'static' | |
| let currentColor = 'black'; | |
| let currentModel = '1'; | |
| let currentBand = 'leather'; | |
| let currentSize = 100; | |
| let handTrackingActive = false; | |
| let lastWristPosition = null; | |
| // Initialize MediaPipe Hands | |
| const hands = new Hands({ | |
| locateFile: (file) => { | |
| return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`; | |
| } | |
| }); | |
| hands.setOptions({ | |
| maxNumHands: 1, | |
| modelComplexity: 1, | |
| minDetectionConfidence: 0.5, | |
| minTrackingConfidence: 0.5 | |
| }); | |
| hands.onResults(onHandResults); | |
| // Initialize | |
| updateWatchDisplay(); | |
| showInstructions(); | |
| // Event Listeners | |
| tryOnBtn.addEventListener('click', activateCameraMode); | |
| staticBtn.addEventListener('click', activateStaticMode); | |
| colorOptions.forEach(option => { | |
| option.addEventListener('click', function() { | |
| colorOptions.forEach(opt => opt.classList.remove('selected')); | |
| this.classList.add('selected'); | |
| currentColor = this.dataset.color; | |
| updateWatchDisplay(); | |
| }); | |
| }); | |
| modelOptions.forEach(option => { | |
| option.addEventListener('click', function() { | |
| modelOptions.forEach(opt => opt.classList.remove('selected')); | |
| this.classList.add('selected'); | |
| currentModel = this.dataset.model; | |
| updateWatchDisplay(); | |
| }); | |
| }); | |
| bandOptions.forEach(option => { | |
| option.addEventListener('click', function() { | |
| bandOptions.forEach(opt => opt.classList.remove('selected')); | |
| this.classList.add('selected'); | |
| currentBand = this.dataset.band; | |
| updateWatchDisplay(); | |
| }); | |
| }); | |
| sizeSlider.addEventListener('input', function() { | |
| currentSize = parseInt(this.value); | |
| updateWatchDisplay(); | |
| }); | |
| sizeDown.addEventListener('click', function() { | |
| currentSize = Math.max(70, currentSize - 5); | |
| sizeSlider.value = currentSize; | |
| updateWatchDisplay(); | |
| }); | |
| sizeUp.addEventListener('click', function() { | |
| currentSize = Math.min(130, currentSize + 5); | |
| sizeSlider.value = currentSize; | |
| updateWatchDisplay(); | |
| }); | |
| closeInstructions.addEventListener('click', function() { | |
| instructionsModal.classList.add('hidden'); | |
| }); | |
| // Functions | |
| function updateWatchDisplay() { | |
| // Apply color filter | |
| watchOverlay.style.filter = colorFilters[currentColor]; | |
| // Apply size | |
| const sizePercent = currentSize / 100; | |
| const model = models[currentModel]; | |
| watchOverlay.style.width = `${model.width * sizePercent}px`; | |
| watchOverlay.style.height = `${model.height * sizePercent}px`; | |
| // Update watch image | |
| watchOverlay.src = model.image; | |
| // If we have a wrist position from hand tracking, update watch position | |
| if (lastWristPosition) { | |
| positionWatchOnWrist(lastWristPosition); | |
| } | |
| } | |
| function activateCameraMode() { | |
| currentMode = 'camera'; | |
| tryOnBtn.classList.remove('bg-gray-200', 'text-gray-800'); | |
| tryOnBtn.classList.add('bg-blue-600', 'text-white'); | |
| staticBtn.classList.remove('bg-blue-600', 'text-white'); | |
| staticBtn.classList.add('bg-gray-200', 'text-gray-800'); | |
| watchOverlay.style.display = 'block'; | |
| outputCanvas.style.display = 'none'; // We don't need to show the hand landmarks | |
| cameraFeed.style.display = 'block'; | |
| // Access camera | |
| if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
| navigator.mediaDevices.getUserMedia({ | |
| video: { | |
| facingMode: 'user', | |
| width: { ideal: 1280 }, | |
| height: { ideal: 720 } | |
| } | |
| }) | |
| .then(function(stream) { | |
| cameraFeed.srcObject = stream; | |
| handTrackingActive = true; | |
| // Process each frame with MediaPipe | |
| const cameraLoop = () => { | |
| if (!handTrackingActive) return; | |
| // Skip if video isn't ready yet | |
| if (cameraFeed.readyState < 2) { | |
| requestAnimationFrame(cameraLoop); | |
| return; | |
| } | |
| try { | |
| hands.send({image: cameraFeed}); | |
| } catch (e) { | |
| console.error("Error processing frame:", e); | |
| } | |
| requestAnimationFrame(cameraLoop); | |
| }; | |
| cameraLoop(); | |
| }) | |
| .catch(function(error) { | |
| console.error("Camera access error:", error); | |
| alert("Could not access the camera. Please check permissions."); | |
| activateStaticMode(); | |
| }); | |
| } else { | |
| alert("Camera access is not supported by your browser."); | |
| activateStaticMode(); | |
| } | |
| } | |
| function activateStaticMode() { | |
| currentMode = 'static'; | |
| staticBtn.classList.remove('bg-gray-200', 'text-gray-800'); | |
| staticBtn.classList.add('bg-blue-600', 'text-white'); | |
| tryOnBtn.classList.remove('bg-blue-600', 'text-white'); | |
| tryOnBtn.classList.add('bg-gray-200', 'text-gray-800'); | |
| watchOverlay.style.display = 'block'; | |
| outputCanvas.style.display = 'none'; | |
| cameraFeed.style.display = 'none'; | |
| // Position watch in the center for static mode | |
| const container = document.querySelector('.watch-container'); | |
| const containerRect = container.getBoundingClientRect(); | |
| watchOverlay.style.left = `${(containerRect.width - watchOverlay.width) / 2}px`; | |
| watchOverlay.style.top = `${(containerRect.height - watchOverlay.height) / 2}px`; | |
| // Stop camera stream if active | |
| if (cameraFeed.srcObject) { | |
| handTrackingActive = false; | |
| cameraFeed.srcObject.getTracks().forEach(track => track.stop()); | |
| cameraFeed.srcObject = null; | |
| } | |
| } | |
| function onHandResults(results) { | |
| // Hide loading overlay once we get first results | |
| if (loadingOverlay.style.display !== 'none') { | |
| loadingOverlay.style.display = 'none'; | |
| } | |
| if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) { | |
| const landmarks = results.multiHandLandmarks[0]; | |
| // Calculate wrist position (landmarks 0 is wrist) | |
| const wrist = landmarks[0]; | |
| // Calculate wrist width using landmarks 1 and 5 (base of index and pinky fingers) | |
| const baseIndex = landmarks[5]; | |
| const basePinky = landmarks[17]; | |
| // Convert normalized coordinates to pixel coordinates | |
| const videoWidth = cameraFeed.videoWidth; | |
| const videoHeight = cameraFeed.videoHeight; | |
| const container = document.querySelector('.watch-container'); | |
| const containerRect = container.getBoundingClientRect(); | |
| const scaleX = containerRect.width / videoWidth; | |
| const scaleY = containerRect.height / videoHeight; | |
| const wristX = wrist.x * videoWidth * scaleX; | |
| const wristY = wrist.y * videoHeight * scaleY; | |
| const baseIndexX = baseIndex.x * videoWidth * scaleX; | |
| const baseIndexY = baseIndex.y * videoHeight * scaleY; | |
| const basePinkyX = basePinky.x * videoWidth * scaleX; | |
| const basePinkyY = basePinky.y * videoHeight * scaleY; | |
| // Calculate wrist width (distance between base of index and pinky) | |
| const wristWidth = Math.sqrt( | |
| Math.pow(basePinkyX - baseIndexX, 2) + | |
| Math.pow(basePinkyY - baseIndexY, 2) | |
| ); | |
| // Calculate wrist angle (for proper watch orientation) | |
| const wristAngle = Math.atan2( | |
| basePinkyY - baseIndexY, | |
| basePinkyX - baseIndexX | |
| ) * 180 / Math.PI; | |
| // Save wrist position data | |
| lastWristPosition = { | |
| x: wristX, | |
| y: wristY, | |
| width: wristWidth, | |
| angle: wristAngle | |
| }; | |
| // Position the watch on the wrist | |
| positionWatchOnWrist(lastWristPosition); | |
| } else { | |
| // No hand detected | |
| watchOverlay.style.display = 'none'; | |
| } | |
| } | |
| function positionWatchOnWrist(wristData) { | |
| const model = models[currentModel]; | |
| const sizePercent = currentSize / 100; | |
| // Calculate watch width based on wrist width and model ratio | |
| const watchWidth = wristData.width * model.wristRatio * sizePercent; | |
| const watchHeight = watchWidth * (model.height / model.width); | |
| // Position watch in the middle of the wrist | |
| const watchX = wristData.x - watchWidth / 2; | |
| const watchY = wristData.y - watchHeight / 2; | |
| // Apply styles to watch overlay | |
| watchOverlay.style.width = `${watchWidth}px`; | |
| watchOverlay.style.height = `${watchHeight}px`; | |
| watchOverlay.style.left = `${watchX}px`; | |
| watchOverlay.style.top = `${watchY}px`; | |
| watchOverlay.style.transform = `rotate(${wristData.angle}deg)`; | |
| watchOverlay.style.display = 'block'; | |
| } | |
| function showInstructions() { | |
| // Only show instructions if this is the first time | |
| if (!localStorage.getItem('watchesInstructionsShown')) { | |
| instructionsModal.classList.remove('hidden'); | |
| localStorage.setItem('watchesInstructionsShown', 'true'); | |
| } | |
| } | |
| }); | |
| </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=farrahred/watch-try-on" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
| </html> |