Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Haircut Advisor - Find Your Perfect Style</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> | |
.face-outline { | |
position: relative; | |
width: 300px; | |
height: 400px; | |
border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%; | |
background: #f3e7d3; | |
margin: 0 auto; | |
box-shadow: 0 10px 25px rgba(0,0,0,0.2); | |
} | |
.face-landmark { | |
position: absolute; | |
background: rgba(255,0,0,0.3); | |
border-radius: 50%; | |
} | |
.face-shape-indicator { | |
position: absolute; | |
border: 2px dashed #4f46e5; | |
border-radius: 50%; | |
} | |
.hair-preview { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
top: 0; | |
left: 0; | |
z-index: 10; | |
pointer-events: none; | |
} | |
.result-card { | |
transition: all 0.3s ease; | |
} | |
.result-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 15px 30px rgba(0,0,0,0.15); | |
} | |
#previewCanvas { | |
display: none; | |
} | |
.upload-area { | |
border: 2px dashed #cbd5e1; | |
transition: all 0.3s ease; | |
} | |
.upload-area:hover { | |
border-color: #818cf8; | |
background-color: #f8fafc; | |
} | |
.upload-area.dragover { | |
border-color: #4f46e5; | |
background-color: #eef2ff; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-12"> | |
<!-- Header --> | |
<header class="text-center mb-12"> | |
<h1 class="text-4xl md:text-5xl font-bold text-indigo-800 mb-3">Haircut Advisor</h1> | |
<p class="text-lg text-gray-600 max-w-2xl mx-auto">Upload your photo and discover the perfect hairstyle that complements your facial features and proportions.</p> | |
</header> | |
<!-- Main Content --> | |
<div class="flex flex-col lg:flex-row gap-8"> | |
<!-- Upload Section --> | |
<div class="lg:w-1/2 bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Analyze Your Face</h2> | |
<div id="uploadArea" class="upload-area rounded-lg p-8 text-center cursor-pointer mb-6"> | |
<div class="flex flex-col items-center justify-center"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i> | |
<p class="text-gray-600 mb-2">Drag & drop your photo here</p> | |
<p class="text-sm text-gray-500 mb-4">or</p> | |
<label for="fileInput" class="bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-full cursor-pointer transition"> | |
Browse Files | |
</label> | |
<input id="fileInput" type="file" accept="image/*" class="hidden"> | |
</div> | |
</div> | |
<div id="facePreview" class="hidden"> | |
<div class="flex flex-col md:flex-row gap-6 items-center"> | |
<div class="relative"> | |
<div class="face-outline"> | |
<canvas id="previewCanvas" width="300" height="400"></canvas> | |
<div id="faceLandmarks" class="hair-preview"></div> | |
</div> | |
</div> | |
<div class="flex-1"> | |
<h3 class="text-xl font-medium text-gray-800 mb-3">Facial Analysis</h3> | |
<div id="faceStats" class="space-y-3"> | |
<!-- Will be populated by JS --> | |
</div> | |
</div> | |
</div> | |
<button id="analyzeBtn" class="mt-6 w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-6 rounded-lg transition"> | |
<i class="fas fa-magic mr-2"></i> Suggest Hairstyles | |
</button> | |
</div> | |
</div> | |
<!-- Results Section --> | |
<div class="lg:w-1/2"> | |
<div id="resultsContainer" class="hidden bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Recommended Hairstyles</h2> | |
<div id="faceShapeInfo" class="mb-6 p-4 bg-indigo-50 rounded-lg"> | |
<h3 class="text-lg font-medium text-indigo-800 mb-2">Your Face Shape: <span id="faceShapeLabel" class="font-bold">Oval</span></h3> | |
<p id="faceShapeDesc" class="text-gray-700">Based on your facial proportions, these hairstyles will best complement your features.</p> | |
</div> | |
<div id="resultsGrid" class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
<!-- Will be populated by JS --> | |
</div> | |
</div> | |
<div id="instructions" class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-2xl font-semibold text-gray-800 mb-4">How It Works</h2> | |
<ol class="space-y-4"> | |
<li class="flex items-start"> | |
<span class="flex items-center justify-center bg-indigo-100 text-indigo-800 rounded-full w-6 h-6 mr-3 font-medium">1</span> | |
<span>Upload a clear front-facing photo with good lighting</span> | |
</li> | |
<li class="flex items-start"> | |
<span class="flex items-center justify-center bg-indigo-100 text-indigo-800 rounded-full w-6 h-6 mr-3 font-medium">2</span> | |
<span>Our system analyzes your facial proportions and shape</span> | |
</li> | |
<li class="flex items-start"> | |
<span class="flex items-center justify-center bg-indigo-100 text-indigo-800 rounded-full w-6 h-6 mr-3 font-medium">3</span> | |
<span>Get personalized hairstyle recommendations based on your features</span> | |
</li> | |
</ol> | |
<div class="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200"> | |
<h3 class="text-lg font-medium text-yellow-800 mb-2"><i class="fas fa-lightbulb mr-2"></i> Pro Tip</h3> | |
<p class="text-yellow-700">For best results, remove glasses and pull hair back to clearly show your facial structure.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const fileInput = document.getElementById('fileInput'); | |
const uploadArea = document.getElementById('uploadArea'); | |
const facePreview = document.getElementById('facePreview'); | |
const previewCanvas = document.getElementById('previewCanvas'); | |
const faceLandmarks = document.getElementById('faceLandmarks'); | |
const faceStats = document.getElementById('faceStats'); | |
const analyzeBtn = document.getElementById('analyzeBtn'); | |
const resultsContainer = document.getElementById('resultsContainer'); | |
const resultsGrid = document.getElementById('resultsGrid'); | |
const faceShapeLabel = document.getElementById('faceShapeLabel'); | |
const faceShapeDesc = document.getElementById('faceShapeDesc'); | |
const instructions = document.getElementById('instructions'); | |
// Drag and drop functionality | |
uploadArea.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.add('dragover'); | |
}); | |
uploadArea.addEventListener('dragleave', () => { | |
uploadArea.classList.remove('dragover'); | |
}); | |
uploadArea.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.remove('dragover'); | |
if (e.dataTransfer.files.length) { | |
fileInput.files = e.dataTransfer.files; | |
handleFileUpload(e.dataTransfer.files[0]); | |
} | |
}); | |
// File input change handler | |
fileInput.addEventListener('change', () => { | |
if (fileInput.files.length) { | |
handleFileUpload(fileInput.files[0]); | |
} | |
}); | |
// Analyze button click handler | |
analyzeBtn.addEventListener('click', analyzeFaceAndSuggestHairstyles); | |
// Handle file upload | |
function handleFileUpload(file) { | |
if (!file.type.match('image.*')) { | |
alert('Please upload an image file.'); | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
// Show preview | |
const img = new Image(); | |
img.onload = function() { | |
const ctx = previewCanvas.getContext('2d'); | |
// Calculate dimensions to fit in canvas while maintaining aspect ratio | |
const hRatio = previewCanvas.width / img.width; | |
const vRatio = previewCanvas.height / img.height; | |
const ratio = Math.min(hRatio, vRatio); | |
const centerShiftX = (previewCanvas.width - img.width * ratio) / 2; | |
const centerShiftY = (previewCanvas.height - img.height * ratio) / 2; | |
ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); | |
ctx.drawImage(img, 0, 0, img.width, img.height, | |
centerShiftX, centerShiftY, img.width * ratio, img.height * ratio); | |
// Show the preview section | |
facePreview.classList.remove('hidden'); | |
uploadArea.classList.add('hidden'); | |
// Simulate face detection (in a real app, you'd use a face detection API) | |
simulateFaceDetection(); | |
}; | |
img.src = e.target.result; | |
}; | |
reader.readAsDataURL(file); | |
} | |
// Simulate face detection (in a real app, replace with actual face detection) | |
function simulateFaceDetection() { | |
// Generate random facial proportions for demo purposes | |
const faceWidth = 0.7 + Math.random() * 0.1; | |
const faceLength = 1.0 + Math.random() * 0.2; | |
const foreheadHeight = 0.3 + Math.random() * 0.1; | |
const cheekboneWidth = 0.6 + Math.random() * 0.1; | |
const jawWidth = 0.5 + Math.random() * 0.15; | |
// Calculate ratios | |
const lengthToWidthRatio = faceLength / faceWidth; | |
const jawToCheekRatio = jawWidth / cheekboneWidth; | |
// Display facial analysis | |
faceStats.innerHTML = ` | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Face Length/Width Ratio:</span> | |
<span class="font-medium">${lengthToWidthRatio.toFixed(2)}</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Jaw/Cheekbone Ratio:</span> | |
<span class="font-medium">${jawToCheekRatio.toFixed(2)}</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Forehead Height:</span> | |
<span class="font-medium">${(foreheadHeight * 100).toFixed(0)}%</span> | |
</div> | |
<div class="flex justify-between"> | |
<span class="text-gray-600">Cheekbone Prominence:</span> | |
<span class="font-medium">${(cheekboneWidth * 100).toFixed(0)}%</span> | |
</div> | |
`; | |
// Add simulated face landmarks | |
faceLandmarks.innerHTML = ''; | |
// Forehead | |
addLandmark(150, 80, 60, 30, 'Forehead'); | |
// Eyes | |
addLandmark(110, 150, 40, 20, 'Left Eye'); | |
addLandmark(190, 150, 40, 20, 'Right Eye'); | |
// Nose | |
addLandmark(150, 200, 20, 40, 'Nose'); | |
// Mouth | |
addLandmark(150, 250, 50, 15, 'Mouth'); | |
// Jawline | |
addLandmark(100, 300, 20, 20, 'Jaw'); | |
addLandmark(200, 300, 20, 20, 'Jaw'); | |
// Face shape indicator | |
const shapeIndicator = document.createElement('div'); | |
shapeIndicator.className = 'face-shape-indicator'; | |
shapeIndicator.style.width = `${faceWidth * 200}px`; | |
shapeIndicator.style.height = `${faceLength * 300}px`; | |
shapeIndicator.style.left = `${150 - (faceWidth * 200)/2}px`; | |
shapeIndicator.style.top = `${100 - (faceLength * 300)/4}px`; | |
faceLandmarks.appendChild(shapeIndicator); | |
} | |
function addLandmark(left, top, width, height, label) { | |
const landmark = document.createElement('div'); | |
landmark.className = 'face-landmark'; | |
landmark.style.width = `${width}px`; | |
landmark.style.height = `${height}px`; | |
landmark.style.left = `${left - width/2}px`; | |
landmark.style.top = `${top - height/2}px`; | |
landmark.title = label; | |
faceLandmarks.appendChild(landmark); | |
} | |
// Analyze face and suggest hairstyles | |
function analyzeFaceAndSuggestHairstyles() { | |
// Show loading state | |
analyzeBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Analyzing...'; | |
analyzeBtn.disabled = true; | |
// Simulate API call delay | |
setTimeout(() => { | |
// Determine face shape (simplified for demo) | |
const faceShapes = [ | |
{ name: 'Oval', desc: 'Oval faces are well-balanced and versatile. Most hairstyles work well with this shape.' }, | |
{ name: 'Round', desc: 'Round faces benefit from hairstyles that add height and length to create the illusion of a more oval shape.' }, | |
{ name: 'Square', desc: 'Square faces look great with styles that soften the angular jawline.' }, | |
{ name: 'Heart', desc: 'Heart-shaped faces suit styles that balance the wider forehead with the narrower chin.' }, | |
{ name: 'Long', desc: 'Long faces benefit from styles that add width and volume to the sides.' } | |
]; | |
const randomShape = faceShapes[Math.floor(Math.random() * faceShapes.length)]; | |
faceShapeLabel.textContent = randomShape.name; | |
faceShapeDesc.textContent = randomShape.desc; | |
// Generate hairstyle suggestions | |
const hairstyles = getHairstyleSuggestions(randomShape.name); | |
// Display results | |
resultsGrid.innerHTML = ''; | |
hairstyles.forEach(style => { | |
const card = document.createElement('div'); | |
card.className = 'result-card bg-white rounded-lg overflow-hidden shadow-md border border-gray-100'; | |
card.innerHTML = ` | |
<div class="h-48 bg-gray-200 relative overflow-hidden"> | |
<img src="${style.image}" alt="${style.name}" class="w-full h-full object-cover"> | |
<div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-3"> | |
<h3 class="text-white font-semibold">${style.name}</h3> | |
</div> | |
</div> | |
<div class="p-4"> | |
<p class="text-gray-600 text-sm mb-3">${style.description}</p> | |
<div class="flex justify-between items-center"> | |
<span class="text-xs px-2 py-1 rounded-full ${style.difficulty === 'Easy' ? 'bg-green-100 text-green-800' : | |
style.difficulty === 'Medium' ? 'bg-yellow-100 text-yellow-800' : 'bg-purple-100 text-purple-800'}"> | |
${style.difficulty} | |
</span> | |
<button class="text-xs text-indigo-600 hover:text-indigo-800 font-medium"> | |
<i class="far fa-save mr-1"></i> Save | |
</button> | |
</div> | |
</div> | |
`; | |
resultsGrid.appendChild(card); | |
}); | |
// Show results | |
resultsContainer.classList.remove('hidden'); | |
instructions.classList.add('hidden'); | |
// Reset button | |
analyzeBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Suggest Hairstyles'; | |
analyzeBtn.disabled = false; | |
}, 1500); | |
} | |
// Get hairstyle suggestions based on face shape | |
function getHairstyleSuggestions(faceShape) { | |
// Base hairstyles | |
const allHairstyles = [ | |
{ | |
name: "Classic Layers", | |
description: "Face-framing layers that add movement and texture while maintaining a polished look.", | |
difficulty: "Medium", | |
suits: ["Oval", "Round", "Heart"], | |
image: "https://images.unsplash.com/photo-1595476108010-b4d1f102b1b1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Blunt Bob", | |
description: "A sharp, clean-cut bob that emphasizes jawlines and works well with straight hair.", | |
difficulty: "Easy", | |
suits: ["Square", "Oval"], | |
image: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Long with Side Bangs", | |
description: "Soft side bangs that gracefully frame the face and draw attention to the eyes.", | |
difficulty: "Easy", | |
suits: ["Square", "Long", "Heart"], | |
image: "https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Pixie Cut", | |
description: "A short, chic cut that highlights facial features and works well with delicate bone structure.", | |
difficulty: "Medium", | |
suits: ["Heart", "Oval", "Round"], | |
image: "https://images.unsplash.com/photo-1519699047748-de8e457a634e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Shag with Curtain Bangs", | |
description: "A textured shag cut with face-framing curtain bangs for a modern, effortless look.", | |
difficulty: "Medium", | |
suits: ["Oval", "Round", "Square"], | |
image: "https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Asymmetrical Lob", | |
description: "A longer bob cut with asymmetrical lines that add interest and balance to facial features.", | |
difficulty: "Hard", | |
suits: ["Square", "Heart", "Oval"], | |
image: "https://images.unsplash.com/photo-1544005313-94ddf0286df2?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Voluminous Curls", | |
description: "Full, bouncy curls that add width and balance to longer face shapes.", | |
difficulty: "Medium", | |
suits: ["Long", "Oval", "Square"], | |
image: "https://images.unsplash.com/photo-1492106087820-71f1a00d2b11?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
}, | |
{ | |
name: "Textured Crop", | |
description: "A short, textured cut with piece-y layers for a modern, edgy look.", | |
difficulty: "Hard", | |
suits: ["Round", "Oval", "Heart"], | |
image: "https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=500&q=80" | |
} | |
]; | |
// Filter hairstyles that suit the face shape | |
return allHairstyles.filter(style => style.suits.includes(faceShape)); | |
} | |
}); | |
</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=Rogerjs/haircut-advisor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |