Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>HairFastGAN Wig 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"> | |
| <style> | |
| .camera-container { | |
| position: relative; | |
| width: 100%; | |
| height: 0; | |
| padding-bottom: 75%; | |
| background-color: #f3f4f6; | |
| border-radius: 0.5rem; | |
| overflow: hidden; | |
| } | |
| .camera-video { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| } | |
| .camera-controls { | |
| position: absolute; | |
| bottom: 1rem; | |
| left: 0; | |
| width: 100%; | |
| display: flex; | |
| justify-content: center; | |
| gap: 1rem; | |
| } | |
| .gallery-item { | |
| transition: all 0.2s ease; | |
| } | |
| .gallery-item:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } | |
| .gallery-item.selected { | |
| border: 3px solid #6366f1; | |
| transform: scale(1.05); | |
| } | |
| .result-container { | |
| position: relative; | |
| transition: all 0.3s ease; | |
| } | |
| .result-container.loading::after { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| font-size: 1.5rem; | |
| } | |
| .loading-spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid #f3f3f3; | |
| border-top: 5px solid #6366f1; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .color-picker { | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| appearance: none; | |
| width: 100%; | |
| height: 40px; | |
| background-color: transparent; | |
| border: none; | |
| cursor: pointer; | |
| } | |
| .color-picker::-webkit-color-swatch { | |
| border-radius: 8px; | |
| border: 2px solid #e5e7eb; | |
| } | |
| .color-picker::-moz-color-swatch { | |
| border-radius: 8px; | |
| border: 2px solid #e5e7eb; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| background-color: #e5e7eb; | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background-color: #6366f1; | |
| width: 0%; | |
| transition: width 0.3s ease; | |
| } | |
| .processing-step { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 0.5rem; | |
| color: #6b7280; | |
| } | |
| .processing-step.active { | |
| color: #111827; | |
| font-weight: 500; | |
| } | |
| .processing-step.completed { | |
| color: #10b981; | |
| } | |
| .processing-step i { | |
| margin-right: 0.5rem; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="mb-8"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <h1 class="text-3xl font-bold text-indigo-600">HairFastGAN</h1> | |
| <p class="text-gray-600">AI-Powered Virtual Wig Try-On</p> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="loginBtn" class="px-4 py-2 text-gray-600 hover:text-indigo-600"> | |
| <i class="fas fa-user mr-2"></i>Login | |
| </button> | |
| <button id="creditsBtn" class="px-4 py-2 bg-indigo-100 text-indigo-600 rounded-full hover:bg-indigo-200"> | |
| <i class="fas fa-coins mr-2"></i>5 Credits | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Left Column - Input Section --> | |
| <div class="lg:col-span-2 bg-white rounded-xl shadow-md p-6"> | |
| <div class="flex border-b border-gray-200 mb-6"> | |
| <button class="tab-btn active px-4 py-2 font-medium text-indigo-600 border-b-2 border-indigo-600" data-tab="upload"> | |
| <i class="fas fa-upload mr-2"></i>Upload Photo | |
| </button> | |
| <button class="tab-btn px-4 py-2 font-medium text-gray-500 hover:text-indigo-600" data-tab="camera"> | |
| <i class="fas fa-camera mr-2"></i>Live Camera | |
| </button> | |
| </div> | |
| <!-- Upload Tab --> | |
| <div id="upload" class="tab-content active"> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Upload Your Photo</h3> | |
| <p class="text-gray-500 mb-4">For best results, use a clear front-facing photo with good lighting.</p> | |
| <div class="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-8 bg-gray-50"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
| <p class="text-gray-500 mb-3">Drag & drop your photo here</p> | |
| <p class="text-gray-400 text-sm mb-4">or</p> | |
| <input type="file" id="photoUpload" accept="image/*" class="hidden"> | |
| <button id="uploadBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-folder-open mr-2"></i>Browse Files | |
| </button> | |
| </div> | |
| <div id="uploadPreview" class="mt-4 hidden"> | |
| <h4 class="text-md font-medium text-gray-900 mb-2">Preview</h4> | |
| <div class="relative"> | |
| <img id="uploadedImage" src="" alt="Uploaded photo" class="w-full h-auto rounded-lg max-h-96 object-contain"> | |
| <button id="removeUpload" class="absolute top-2 right-2 bg-white p-2 rounded-full shadow-md hover:bg-gray-100"> | |
| <i class="fas fa-times text-gray-700"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Camera Tab --> | |
| <div id="camera" class="tab-content"> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Live Camera Try-On</h3> | |
| <p class="text-gray-500 mb-4">Allow camera access to try wigs in real-time.</p> | |
| <div class="camera-container"> | |
| <video id="cameraVideo" class="camera-video" autoplay playsinline></video> | |
| <div class="camera-controls"> | |
| <button id="captureBtn" class="p-3 bg-indigo-600 text-white rounded-full hover:bg-indigo-700"> | |
| <i class="fas fa-camera text-xl"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="capturePreview" class="mt-4 hidden"> | |
| <h4 class="text-md font-medium text-gray-900 mb-2">Captured Photo</h4> | |
| <div class="relative"> | |
| <canvas id="capturedImage" class="w-full h-auto rounded-lg max-h-96 object-contain"></canvas> | |
| <button id="retakeBtn" class="absolute top-2 right-2 bg-white p-2 rounded-full shadow-md hover:bg-gray-100"> | |
| <i class="fas fa-redo text-gray-700"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Hairstyle Selection --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Choose Your Wig Style</h3> | |
| <div class="flex border-b border-gray-200 mb-4"> | |
| <button class="style-tab-btn px-4 py-2 font-medium text-gray-500 hover:text-indigo-600" data-style="gallery"> | |
| <i class="fas fa-images mr-2"></i>Style Gallery | |
| </button> | |
| <button class="style-tab-btn px-4 py-2 font-medium text-gray-500 hover:text-indigo-600" data-style="color"> | |
| <i class="fas fa-palette mr-2"></i>Color Only | |
| </button> | |
| <button class="style-tab-btn active px-4 py-2 font-medium text-indigo-600 border-b-2 border-indigo-600" data-style="upload"> | |
| <i class="fas fa-upload mr-2"></i>Upload Your Wig | |
| </button> | |
| </div> | |
| <!-- Style Gallery --> | |
| <div id="gallery" class="style-tab-content active"> | |
| <div class="mb-4"> | |
| <div class="flex overflow-x-auto pb-2 space-x-3"> | |
| <button class="category-btn active px-3 py-1 bg-indigo-600 text-white rounded-full text-sm" data-category="all">All</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="short">Short</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="medium">Medium</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="long">Long</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="curly">Curly</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="straight">Straight</button> | |
| <button class="category-btn px-3 py-1 bg-gray-200 text-gray-700 rounded-full text-sm hover:bg-gray-300" data-category="vip" data-vip="true"> | |
| <i class="fas fa-crown mr-1 text-yellow-500"></i>VIP | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-3"> | |
| <!-- Sample wig styles - in a real app these would come from a database --> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="1" data-category="short straight"> | |
| <img src="/wigs/short-bob.jpg" alt="Short straight wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Short Bob</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="2" data-category="medium curly"> | |
| <img src="/wigs/curly-shoulder.jpg" alt="Medium curly wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Curly Shoulder</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="3" data-category="long straight"> | |
| <img src="/wigs/long-straight.jpg" alt="Long straight wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Long Straight</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="4" data-category="short curly"> | |
| <img src="/wigs/short-curly.jpg" alt="Short curly wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Short Curly</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="5" data-category="medium straight"> | |
| <img src="/wigs/medium-straight.jpg" alt="Medium straight wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Medium Straight</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="6" data-category="long curly"> | |
| <img src="/wigs/long-curly.jpg" alt="Long curly wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs">Long Curly</div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="7" data-category="vip long straight" data-vip="true"> | |
| <img src="/wigs/vip-ombre.jpg" alt="VIP long straight wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs flex items-center justify-center"> | |
| <span>VIP Ombre</span> | |
| <i class="fas fa-crown ml-1 text-yellow-500"></i> | |
| </div> | |
| </div> | |
| <div class="gallery-item rounded-lg overflow-hidden cursor-pointer" data-id="8" data-category="vip medium curly" data-vip="true"> | |
| <img src="/wigs/vip-waves.jpg" alt="VIP medium curly wig" class="w-full h-24 object-cover"> | |
| <div class="p-2 text-center text-xs flex items-center justify-center"> | |
| <span>VIP Waves</span> | |
| <i class="fas fa-crown ml-1 text-yellow-500"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Color Only --> | |
| <div id="color" class="style-tab-content"> | |
| <div class="mb-4"> | |
| <p class="text-gray-500 mb-3">Select a hair color to apply to your current hairstyle:</p> | |
| <input type="color" id="hairColorPicker" class="color-picker" value="#5a3e2a"> | |
| </div> | |
| <div class="grid grid-cols-5 gap-3"> | |
| <div class="color-swatch bg-[#000000] h-10 rounded cursor-pointer" data-color="#000000"></div> | |
| <div class="color-swatch bg-[#5a3e2a] h-10 rounded cursor-pointer" data-color="#5a3e2a"></div> | |
| <div class="color-swatch bg-[#a55728] h-10 rounded cursor-pointer" data-color="#a55728"></div> | |
| <div class="color-swatch bg-[#b58143] h-10 rounded cursor-pointer" data-color="#b58143"></div> | |
| <div class="color-swatch bg-[#d4b996] h-10 rounded cursor-pointer" data-color="#d4b996"></div> | |
| <div class="color-swatch bg-[#f0e2c6] h-10 rounded cursor-pointer" data-color="#f0e2c6"></div> | |
| <div class="color-swatch bg-[#c93384] h-10 rounded cursor-pointer" data-color="#c93384"></div> | |
| <div class="color-swatch bg-[#e64980] h-10 rounded cursor-pointer" data-color="#e64980"></div> | |
| <div class="color-swatch bg-[#6741d9] h-10 rounded cursor-pointer" data-color="#6741d9"></div> | |
| <div class="color-swatch bg-[#228be6] h-10 rounded cursor-pointer" data-color="#228be6"></div> | |
| </div> | |
| </div> | |
| <!-- Upload Style --> | |
| <div id="uploadStyle" class="style-tab-content active"> | |
| <div class="mb-4"> | |
| <p class="text-gray-500 mb-3">Upload a photo of the wig you want to try on:</p> | |
| <div class="flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg p-8 bg-gray-50 hover:bg-gray-100 transition-colors duration-200"> | |
| <i class="fas fa-wig text-4xl text-indigo-400 mb-3"></i> | |
| <p class="text-gray-500 mb-3 font-medium">Drag & drop wig photo here</p> | |
| <p class="text-gray-400 text-sm mb-4">or</p> | |
| <input type="file" id="styleUpload" accept="image/*" class="hidden"> | |
| <button id="uploadStyleBtn" class="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors duration-200"> | |
| <i class="fas fa-upload mr-2"></i>Select Wig Image | |
| </button> | |
| <p class="text-gray-400 text-xs mt-3">Supports JPG, PNG up to 5MB</p> | |
| </div> | |
| </div> | |
| <div id="stylePreview" class="hidden mt-6"> | |
| <h4 class="text-lg font-medium text-gray-900 mb-3">Your Selected Wig</h4> | |
| <div class="relative bg-gray-100 rounded-xl p-4"> | |
| <img id="uploadedStyleImage" src="" alt="Uploaded wig" class="w-full h-auto rounded-lg max-h-64 object-contain mx-auto"> | |
| <button id="removeStyle" class="absolute top-4 right-4 bg-white p-2 rounded-full shadow-md hover:bg-gray-100 transition-colors duration-200"> | |
| <i class="fas fa-redo text-gray-700"></i> Change | |
| </button> | |
| </div> | |
| <div class="mt-4 flex items-center justify-between"> | |
| <div> | |
| <h5 class="font-medium">Wig Details</h5> | |
| <p id="wigDetails" class="text-sm text-gray-500">Uploaded wig</p> | |
| </div> | |
| <button id="analyzeWigBtn" class="px-4 py-2 bg-indigo-100 text-indigo-700 rounded-lg hover:bg-indigo-200 transition-colors duration-200"> | |
| <i class="fas fa-search mr-2"></i> Analyze Wig | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Processing Options --> | |
| <div class="mb-6"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Processing Options</h3> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <div> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded text-indigo-600" checked> | |
| <span class="ml-2 text-gray-700">Pose Alignment (Rotate Encoder)</span> | |
| </label> | |
| <p class="text-gray-500 text-sm ml-6">Adjusts head position for better matching</p> | |
| </div> | |
| <div> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded text-indigo-600" checked> | |
| <span class="ml-2 text-gray-700">Shape Alignment (SEAN + StyleGAN)</span> | |
| </label> | |
| <p class="text-gray-500 text-sm ml-6">Matches the wig shape to your face</p> | |
| </div> | |
| <div> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded text-indigo-600" checked> | |
| <span class="ml-2 text-gray-700">Color Alignment (CLIP-guided S editing)</span> | |
| </label> | |
| <p class="text-gray-500 text-sm ml-6">Ensures natural-looking color transfer</p> | |
| </div> | |
| <div> | |
| <label class="flex items-center"> | |
| <input type="checkbox" class="rounded text-indigo-600" checked> | |
| <span class="ml-2 text-gray-700">Detail Refinement (64x64 FS fusion)</span> | |
| </label> | |
| <p class="text-gray-500 text-sm ml-6">Restores facial identity and details</p> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="generateBtn" class="w-full py-3 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 flex items-center justify-center"> | |
| <i class="fas fa-magic mr-2"></i>Generate Wig Try-On (1 Credit) | |
| </button> | |
| </div> | |
| <!-- Right Column - Results Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-bold text-gray-900 mb-4">Your Results</h2> | |
| <div id="emptyState" class="flex flex-col items-center justify-center py-12"> | |
| <i class="fas fa-cut text-5xl text-gray-300 mb-4"></i> | |
| <h3 class="text-lg font-medium text-gray-700 mb-2">No results yet</h3> | |
| <p class="text-gray-500 text-center mb-4">Upload your photo and select a wig style to see how it would look on you!</p> | |
| </div> | |
| <div id="resultContainer" class="hidden"> | |
| <div class="result-container mb-4"> | |
| <img id="resultImage" src="" alt="Wig try-on result" class="w-full h-auto rounded-lg"> | |
| </div> | |
| <div class="flex justify-between mb-4"> | |
| <div> | |
| <h4 class="font-medium text-gray-900">Your Selected Style</h4> | |
| <p id="selectedStyleName" class="text-gray-500 text-sm">Short Bob</p> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="downloadBtn" class="p-2 bg-gray-100 rounded-full hover:bg-gray-200"> | |
| <i class="fas fa-download text-gray-700"></i> | |
| </button> | |
| <button id="saveBtn" class="p-2 bg-gray-100 rounded-full hover:bg-gray-200"> | |
| <i class="fas fa-save text-gray-700"></i> | |
| </button> | |
| <button id="shareBtn" class="p-2 bg-gray-100 rounded-full hover:bg-gray-200"> | |
| <i class="fas fa-share-alt text-gray-700"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <h4 class="font-medium text-gray-900 mb-2">Try Another Style</h4> | |
| <div class="grid grid-cols-4 gap-2"> | |
| <div class="rounded overflow-hidden cursor-pointer"> | |
| <img src="/wigs/short-bob-thumb.jpg" alt="Short straight wig" class="w-full h-16 object-cover"> | |
| </div> | |
| <div class="rounded overflow-hidden cursor-pointer"> | |
| <img src="/wigs/curly-shoulder-thumb.jpg" alt="Medium curly wig" class="w-full h-16 object-cover"> | |
| </div> | |
| <div class="rounded overflow-hidden cursor-pointer"> | |
| <img src="/wigs/long-straight-thumb.jpg" alt="Long straight wig" class="w-full h-16 object-cover"> | |
| </div> | |
| <div class="rounded overflow-hidden cursor-pointer"> | |
| <img src="/wigs/short-curly-thumb.jpg" alt="Short curly wig" class="w-full h-16 object-cover"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-indigo-50 rounded-lg p-4"> | |
| <h4 class="font-medium text-indigo-800 mb-2">Love this style?</h4> | |
| <p class="text-indigo-700 text-sm mb-3">Get this exact wig customized to your measurements.</p> | |
| <button id="buyBtn" class="w-full py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700"> | |
| Order Custom Wig ($89.99) | |
| </button> | |
| </div> | |
| </div> | |
| <div id="processingContainer" class="hidden"> | |
| <div class="flex flex-col items-center justify-center py-8"> | |
| <div class="loading-spinner mb-4"></div> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Processing Your Image</h3> | |
| <p class="text-gray-500 text-center mb-6">Our AI is applying your selected wig style...</p> | |
| <div class="w-full mb-6"> | |
| <div class="progress-bar"> | |
| <div id="progressFill" class="progress-fill"></div> | |
| </div> | |
| </div> | |
| <div class="w-full space-y-2"> | |
| <div class="processing-step active"> | |
| <i class="fas fa-sync-alt"></i> | |
| <span>Pose alignment using Rotate Encoder</span> | |
| </div> | |
| <div class="processing-step"> | |
| <i class="fas fa-project-diagram"></i> | |
| <span>Shape alignment in FS space</span> | |
| </div> | |
| <div class="processing-step"> | |
| <i class="fas fa-palette"></i> | |
| <span>Color alignment with CLIP guidance</span> | |
| </div> | |
| <div class="processing-step"> | |
| <i class="fas fa-magic"></i> | |
| <span>Detail refinement with 64x64 FS fusion</span> | |
| </div> | |
| <div class="processing-step"> | |
| <i class="fas fa-check-circle"></i> | |
| <span>Final rendering</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- VIP Modal --> | |
| <div id="vipModal" 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 w-full"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-bold text-gray-900">VIP Styles</h3> | |
| <button id="closeVipModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <p class="text-gray-600">VIP styles are premium designs that require additional credits to unlock.</p> | |
| </div> | |
| <div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-crown text-yellow-500 mt-1"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <h3 class="text-sm font-medium text-yellow-800">VIP Style Detected</h3> | |
| <div class="mt-2 text-sm text-yellow-700"> | |
| <p>This style requires 3 credits to try on.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <button id="useCreditsBtn" class="py-2 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700"> | |
| Use 3 Credits | |
| </button> | |
| <button id="buyCreditsBtn" class="py-2 bg-yellow-500 text-white rounded-lg font-medium hover:bg-yellow-600"> | |
| Buy More Credits | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Credits Modal --> | |
| <div id="creditsModal" 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 w-full"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-bold text-gray-900">Buy More Credits</h3> | |
| <button id="closeCreditsModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <p class="text-gray-600">Each credit allows you to generate one wig try-on result.</p> | |
| </div> | |
| <div class="space-y-3 mb-6"> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:border-indigo-300"> | |
| <div> | |
| <h4 class="font-medium text-gray-900">10 Credits</h4> | |
| <p class="text-sm text-gray-500">$9.99 ($1.00 per credit)</p> | |
| </div> | |
| <button class="px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700"> | |
| Select | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:border-indigo-300"> | |
| <div> | |
| <h4 class="font-medium text-gray-900">25 Credits</h4> | |
| <p class="text-sm text-gray-500">$19.99 ($0.80 per credit)</p> | |
| </div> | |
| <button class="px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700"> | |
| Select | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:border-indigo-300 bg-indigo-50 border-indigo-200"> | |
| <div> | |
| <h4 class="font-medium text-indigo-900">50 Credits</h4> | |
| <p class="text-sm text-indigo-700">$29.99 ($0.60 per credit)</p> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="bg-indigo-600 text-white text-xs font-medium px-2 py-0.5 rounded mr-2">Best Value</span> | |
| <button class="px-4 py-2 bg-indigo-600 text-white rounded-lg text-sm font-medium hover:bg-indigo-700"> | |
| Select | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-center"> | |
| <button id="subscribeBtn" class="text-indigo-600 hover:text-indigo-800 text-sm font-medium"> | |
| Or subscribe for unlimited credits at $14.99/month | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const tabBtns = document.querySelectorAll('.tab-btn'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| const styleTabBtns = document.querySelectorAll('.style-tab-btn'); | |
| const styleTabContents = document.querySelectorAll('.style-tab-content'); | |
| const categoryBtns = document.querySelectorAll('.category-btn'); | |
| const galleryItems = document.querySelectorAll('.gallery-item'); | |
| const colorSwatches = document.querySelectorAll('.color-swatch'); | |
| const uploadBtn = document.getElementById('uploadBtn'); | |
| const photoUpload = document.getElementById('photoUpload'); | |
| const uploadPreview = document.getElementById('uploadPreview'); | |
| const uploadedImage = document.getElementById('uploadedImage'); | |
| const removeUpload = document.getElementById('removeUpload'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const emptyState = document.getElementById('emptyState'); | |
| const resultContainer = document.getElementById('resultContainer'); | |
| const resultImage = document.getElementById('resultImage'); | |
| const processingContainer = document.getElementById('processingContainer'); | |
| const progressFill = document.getElementById('progressFill'); | |
| const processingSteps = document.querySelectorAll('.processing-step'); | |
| const vipModal = document.getElementById('vipModal'); | |
| const closeVipModal = document.getElementById('closeVipModal'); | |
| const useCreditsBtn = document.getElementById('useCreditsBtn'); | |
| const buyCreditsBtn = document.getElementById('buyCreditsBtn'); | |
| const creditsBtn = document.getElementById('creditsBtn'); | |
| const creditsModal = document.getElementById('creditsModal'); | |
| const closeCreditsModal = document.getElementById('closeCreditsModal'); | |
| const captureBtn = document.getElementById('captureBtn'); | |
| const cameraVideo = document.getElementById('cameraVideo'); | |
| const capturedImage = document.getElementById('capturedImage'); | |
| const capturePreview = document.getElementById('capturePreview'); | |
| const retakeBtn = document.getElementById('retakeBtn'); | |
| const uploadStyleBtn = document.getElementById('uploadStyleBtn'); | |
| const styleUpload = document.getElementById('styleUpload'); | |
| const stylePreview = document.getElementById('stylePreview'); | |
| const uploadedStyleImage = document.getElementById('uploadedStyleImage'); | |
| const removeStyle = document.getElementById('removeStyle'); | |
| const hairColorPicker = document.getElementById('hairColorPicker'); | |
| const selectedStyleName = document.getElementById('selectedStyleName'); | |
| // Tab switching | |
| tabBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| tabBtns.forEach(b => b.classList.remove('active', 'text-indigo-600', 'border-indigo-600')); | |
| btn.classList.add('active', 'text-indigo-600', 'border-indigo-600'); | |
| const tabId = btn.getAttribute('data-tab'); | |
| tabContents.forEach(content => { | |
| content.classList.remove('active'); | |
| if (content.id === tabId) { | |
| content.classList.add('active'); | |
| // Initialize camera if camera tab is selected | |
| if (tabId === 'camera') { | |
| initCamera(); | |
| } else { | |
| stopCamera(); | |
| } | |
| } | |
| }); | |
| }); | |
| }); | |
| // Style tab switching | |
| styleTabBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| styleTabBtns.forEach(b => b.classList.remove('active', 'text-indigo-600', 'border-indigo-600')); | |
| btn.classList.add('active', 'text-indigo-600', 'border-indigo-600'); | |
| const styleTabId = btn.getAttribute('data-style'); | |
| styleTabContents.forEach(content => { | |
| content.classList.remove('active'); | |
| if (content.id === styleTabId) { | |
| content.classList.add('active'); | |
| } | |
| }); | |
| }); | |
| }); | |
| // Category filtering | |
| categoryBtns.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| categoryBtns.forEach(b => b.classList.remove('active', 'bg-indigo-600', 'text-white')); | |
| btn.classList.add('active', 'bg-indigo-600', 'text-white'); | |
| const category = btn.getAttribute('data-category'); | |
| const isVip = btn.getAttribute('data-vip') === 'true'; | |
| galleryItems.forEach(item => { | |
| const itemCategories = item.getAttribute('data-category').split(' '); | |
| const itemIsVip = item.getAttribute('data-vip') === 'true'; | |
| if (category === 'all' || | |
| itemCategories.includes(category) || | |
| (isVip && itemIsVip)) { | |
| item.style.display = 'block'; | |
| } else { | |
| item.style.display = 'none'; | |
| } | |
| }); | |
| }); | |
| }); | |
| // Gallery item selection | |
| galleryItems.forEach(item => { | |
| item.addEventListener('click', () => { | |
| // Check if VIP style | |
| const isVip = item.getAttribute('data-vip') === 'true'; | |
| if (isVip) { | |
| // Show VIP modal | |
| vipModal.classList.remove('hidden'); | |
| return; | |
| } | |
| galleryItems.forEach(i => i.classList.remove('selected')); | |
| item.classList.add('selected'); | |
| // Update selected style name | |
| const styleName = item.querySelector('div').textContent.trim(); | |
| selectedStyleName.textContent = styleName; | |
| }); | |
| }); | |
| // Color swatch selection | |
| colorSwatches.forEach(swatch => { | |
| swatch.addEventListener('click', () => { | |
| const color = swatch.getAttribute('data-color'); | |
| hairColorPicker.value = color; | |
| // Highlight selected swatch | |
| colorSwatches.forEach(s => s.classList.remove('ring-2', 'ring-indigo-500', 'ring-offset-2')); | |
| swatch.classList.add('ring-2', 'ring-indigo-500', 'ring-offset-2'); | |
| }); | |
| }); | |
| // Photo upload | |
| uploadBtn.addEventListener('click', () => photoUpload.click()); | |
| photoUpload.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| const file = e.target.files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| uploadedImage.src = event.target.result; | |
| uploadPreview.classList.remove('hidden'); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // Remove uploaded photo | |
| removeUpload.addEventListener('click', () => { | |
| uploadedImage.src = ''; | |
| uploadPreview.classList.add('hidden'); | |
| photoUpload.value = ''; | |
| }); | |
| // Style upload | |
| uploadStyleBtn.addEventListener('click', () => styleUpload.click()); | |
| styleUpload.addEventListener('change', (e) => { | |
| if (e.target.files.length > 0) { | |
| const file = e.target.files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| uploadedStyleImage.src = event.target.result; | |
| stylePreview.classList.remove('hidden'); | |
| // Update wig details | |
| const wigDetails = document.getElementById('wigDetails'); | |
| wigDetails.textContent = `${file.name} (${(file.size/1024/1024).toFixed(1)}MB)`; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }); | |
| // Remove/change uploaded style | |
| removeStyle.addEventListener('click', () => { | |
| styleUpload.value = ''; | |
| styleUpload.click(); | |
| }); | |
| // Analyze wig button | |
| document.getElementById('analyzeWigBtn').addEventListener('click', () => { | |
| if (uploadedStyleImage.src) { | |
| // Show processing animation | |
| const analyzeBtn = document.getElementById('analyzeWigBtn'); | |
| analyzeBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Analyzing...'; | |
| analyzeBtn.disabled = true; | |
| // Simulate analysis (in real app, this would call backend) | |
| setTimeout(() => { | |
| analyzeBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Analyzed'; | |
| // Update wig details with analysis results | |
| const wigDetails = document.getElementById('wigDetails'); | |
| wigDetails.innerHTML = ` | |
| <span class="font-medium">Long Straight Wig</span><br> | |
| <span class="text-indigo-600">✓ Shape detected</span><br> | |
| <span class="text-indigo-600">✓ Color analyzed (#5a3e2a)</span> | |
| `; | |
| // Enable generate button | |
| document.getElementById('generateBtn').disabled = false; | |
| }, 2000); | |
| } else { | |
| alert('Please upload a wig image first'); | |
| } | |
| }); | |
| // Generate button click | |
| generateBtn.addEventListener('click', () => { | |
| // Check if photo is uploaded | |
| if (!uploadPreview.classList.contains('hidden') || !capturePreview.classList.contains('hidden')) { | |
| // Show processing state | |
| emptyState.classList.add('hidden'); | |
| resultContainer.classList.add('hidden'); | |
| processingContainer.classList.remove('hidden'); | |
| // Simulate processing steps | |
| simulateProcessing(); | |
| // After processing, show result | |
| setTimeout(() => { | |
| processingContainer.classList.add('hidden'); | |
| resultContainer.classList.remove('hidden'); | |
| // Set the generated result from processing | |
| resultImage.src = "/generated-results/result.jpg"; | |
| resultImage.alt = "Generated wig try-on result"; | |
| }, 3000); | |
| } else { | |
| alert('Please upload a photo or take one with your camera first.'); | |
| } | |
| }); | |
| // VIP modal | |
| closeVipModal.addEventListener('click', () => { | |
| vipModal.classList.add('hidden'); | |
| }); | |
| useCreditsBtn.addEventListener('click', () => { | |
| // In a real app, would deduct credits and proceed | |
| alert('VIP style unlocked with 3 credits!'); | |
| vipModal.classList.add('hidden'); | |
| // Find and select the VIP item | |
| galleryItems.forEach(item => { | |
| if (item.getAttribute('data-vip') === 'true' && item.classList.contains('selected')) { | |
| const styleName = item.querySelector('div').textContent.trim(); | |
| selectedStyleName.textContent = styleName; | |
| } | |
| }); | |
| }); | |
| buyCreditsBtn.addEventListener('click', () => { | |
| vipModal.classList.add('hidden'); | |
| creditsModal.classList.remove('hidden'); | |
| }); | |
| // Credits modal | |
| creditsBtn.addEventListener('click', () => { | |
| creditsModal.classList.remove('hidden'); | |
| }); | |
| closeCreditsModal.addEventListener('click', () => { | |
| creditsModal.classList.add('hidden'); | |
| }); | |
| // Camera functionality | |
| function initCamera() { | |
| if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(stream => { | |
| cameraVideo.srcObject = stream; | |
| }) | |
| .catch(error => { | |
| console.error("Camera access error:", error); | |
| alert("Could not access the camera. Please check permissions."); | |
| }); | |
| } | |
| } | |
| function stopCamera() { | |
| if (cameraVideo.srcObject) { | |
| cameraVideo.srcObject.getTracks().forEach(track => track.stop()); | |
| cameraVideo.srcObject = null; | |
| } | |
| } | |
| // Capture photo from camera | |
| captureBtn.addEventListener('click', () => { | |
| const context = capturedImage.getContext('2d'); | |
| capturedImage.width = cameraVideo.videoWidth; | |
| capturedImage.height = cameraVideo.videoHeight; | |
| context.drawImage(cameraVideo, 0, 0, capturedImage.width, capturedImage.height); | |
| capturePreview.classList.remove('hidden'); | |
| }); | |
| // Retake photo | |
| retakeBtn.addEventListener('click', () => { | |
| capturePreview.classList.add('hidden'); | |
| }); | |
| // Simulate processing steps | |
| function simulateProcessing() { | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| progressFill.style.width = `${progress}%`; | |
| // Update active step | |
| if (progress >= 20) processingSteps[1].classList.add('active'); | |
| if (progress >= 40) { | |
| processingSteps[1].classList.remove('active'); | |
| processingSteps[1].classList.add('completed'); | |
| processingSteps[2].classList.add('active'); | |
| } | |
| if (progress >= 60) { | |
| processingSteps[2].classList.remove('active'); | |
| processingSteps[2].classList.add('completed'); | |
| processingSteps[3].classList.add('active'); | |
| } | |
| if (progress >= 80) { | |
| processingSteps[3].classList.remove('active'); | |
| processingSteps[3].classList.add('completed'); | |
| processingSteps[4].classList.add('active'); | |
| } | |
| if (progress >= 100) { | |
| processingSteps[4].classList.remove('active'); | |
| processingSteps[4].classList.add('completed'); | |
| clearInterval(interval); | |
| } | |
| }, 100); | |
| } | |
| // Initialize with first tab active | |
| document.querySelector('.tab-btn').click(); | |
| document.querySelector('.style-tab-btn').click(); | |
| document.querySelector('.category-btn').click(); | |
| // Clean up camera on page unload | |
| window.addEventListener('beforeunload', () => { | |
| stopCamera(); | |
| }); | |
| </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=savvysmith/wigapp" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |