SneakerAI / templates /index.html
thejagstudio's picture
Update templates/index.html
8fe61e6 verified
raw
history blame
15.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Sneaker Category Predictor</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'neo-black': '#0F1116',
'neo-blue': '#2E3BFF'
}
}
}
}
</script>
<style>
.glass-effect {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.glass-effect:hover {
background: rgba(255, 255, 255, 0.08);
}
.gradient-border {
position: relative;
border: double 1px transparent;
border-radius: 0.5rem;
background-image: linear-gradient(#0F1116, #0F1116),
linear-gradient(to right, #2E3BFF, #7C3AED);
background-origin: border-box;
background-clip: padding-box, border-box;
transition: all 0.3s ease;
}
.gradient-border:hover {
background-image: linear-gradient(#0F1116, #0F1116),
linear-gradient(to right, #3E4BFF, #8C4AFD);
}
.custom-select {
position: relative;
display: inline-block;
width: 100%;
}
.custom-select select {
display: none;
}
.select-selected {
background-color: rgba(255, 255, 255, 0.05);
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
}
.select-items {
position: absolute;
padding: 3px;
top: 100%;
left: 0;
right: 0;
z-index: 99;
background: rgb(0, 0, 0);
backdrop-filter: blur(10px);
border-radius: 0.5rem;
margin-top: 0.5rem;
max-height: 200px;
overflow-y: auto;
display: none;
}
.select-items div {
padding: 0.5rem 1rem;
cursor: pointer;
transition: all 0.2s;
}
.select-items div:hover {
background: #534dad96;
border-radius: 0.5rem;
}
.drop-zone {
border: 2px dashed rgba(46, 59, 255, 0.3);
border-radius: 1rem;
padding: 1rem;
text-align: center;
transition: all 0.3s ease;
}
.drop-zone.drag-over {
border-color: #2E3BFF;
background: rgba(46, 59, 255, 0.1);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
/* Custom scrollbar */
.select-items::-webkit-scrollbar {
width: 6px;
}
.select-items::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
.select-items::-webkit-scrollbar-thumb {
background: rgba(46, 59, 255, 0.5);
border-radius: 3px;
}
.select-search {
padding: 0.5rem;
width: 100%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.25rem;
color: white;
margin-bottom: 0.5rem;
}
.select-search:focus {
outline: none;
border-color: rgba(46, 59, 255, 0.5);
}
.select-option-hidden {
display: none;
}
</style>
</head>
<body class="bg-neo-black text-gray-100 min-h-screen">
<div class="fixed w-full h-full">
<div class="absolute top-0 left-0 w-96 h-96 bg-blue-500 rounded-full filter blur-[128px] opacity-20"></div>
<div class="absolute bottom-0 right-0 w-96 h-96 bg-purple-500 rounded-full filter blur-[128px] opacity-20"></div>
</div>
<div class="container mx-auto px-4 py-8 w-full relative">
<h1
class="text-5xl font-bold text-center mb-12 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-purple-500">
AI Sneaker Predictor
</h1>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="flex flex-col items-center justify-center w-full col-span-1 md:col-span-2">
<div class="glass-effect p-8 rounded-xl space-y-6 w-full h-fit">
<div class="flex items-start justify-center gap-5 w-full">
<div id="dropZone" class="drop-zone h-full aspect-square w-full flex items-center justify-center">
<div id="imagePreview" class="hidden w-full h-full">
<img id="preview" class="w-full h-full rounded-lg shadow-lg border border-blue-500/20 bg-gradient-to-tr from-blue-500/15 to-purple-500/15" alt="Preview">
</div>
<div id="dropText" class="text-blue-300">
<svg class="w-12 h-12 mx-auto mb-4 text-blue-500" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p class="text-lg">Drag and drop your sneaker image here</p>
<p class="text-sm text-blue-400 mt-2">or click to browse</p>
<input type="file" id="imageUpload" class="hidden" accept="image/*">
</div>
</div>
<div class="space-y-6 w-full">
<div class="custom-select">
<label class="block text-sm font-medium text-blue-300 mb-2">Brand</label>
<select id="brand" required>
{% for brand in metadata.brand %}
<option value="{{brand}}">{{brand}}</option>
{% endfor %}
</select>
</div>
<div class="custom-select">
<label class="block text-sm font-medium text-blue-300 mb-2">Color</label>
<select id="color" required>
{% for color in metadata.color %}
<option value="{{color}}">{{color}}</option>
{% endfor %}
</select>
</div>
<div class="custom-select">
<label class="block text-sm font-medium text-blue-300 mb-2">Gender</label>
<select id="gender" required>
{% for gender in metadata.gender %}
<option value="{{gender}}">{{gender}}</option>
{% endfor %}
</select>
</div>
<div class="custom-select">
<label class="block text-sm font-medium text-blue-300 mb-2">Midsole</label>
<select id="midsole" required>
{% for midsole in metadata.midsole %}
<option value="{{midsole}}">{% if midsole == "" %}Null{%else%}{{midsole}}{%endif%}</option>
{% endfor %}
</select>
</div>
<div class="custom-select">
<label class="block text-sm font-medium text-blue-300 mb-2">Upper Material</label>
<select id="upperMaterial" required>
{% for upperMaterial in metadata.upperMaterial %}
<option value="{{upperMaterial}}">{% if upperMaterial == "" %}Null{%else%}{{upperMaterial}}{%endif%}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<button
class="w-full py-4 px-6 rounded-lg font-medium transition-all duration-300 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 focus:ring-offset-neo-black transform hover:scale-105"
onclick="predict()">
<div class="flex items-center justify-center space-x-3">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
<span class="text-lg">Predict Category</span>
</div>
</button>
</div>
<div class="mt-8">
<h3 class="text-xl font-semibold mb-4 text-blue-300">Example Sneakers</h3>
<div class="grid grid-cols-3 gap-4">
{% for product in products %}
<div class="glass-effect p-4 rounded-xl cursor-pointer hover:scale-105 transition-transform" onclick="loadExample('{{loop.index0}}')">
<img src="{{product.img}}" alt="{{product.name}}" class="w-full rounded-lg mb-2">
<p class="text-sm text-blue-300">{{product.name}} ({{product.category}})</p>
</div>
{% endfor %}
</div>
</div>
</div>
<div>
<div id="result" class="hidden glass-effect p-6 rounded-xl">
<h3 class="text-xl font-semibold mb-4 text-blue-300">AI Prediction</h3>
<div id="predictions" class="space-y-4">
<!-- Predictions will be inserted here -->
</div>
</div>
</div>
</div>
</div>
<script>
function initCustomSelects() {
document.querySelectorAll('.custom-select select').forEach(select => {
const div = document.createElement('div');
div.classList.add('select-selected', 'gradient-border');
div.textContent = select.options[select.selectedIndex].text;
select.parentElement.appendChild(div);
const itemsDiv = document.createElement('div');
itemsDiv.classList.add('select-items');
// Add search input
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search...';
searchInput.classList.add('select-search');
itemsDiv.appendChild(searchInput);
const optionsContainer = document.createElement('div');
Array.from(select.options).forEach(option => {
const optionDiv = document.createElement('div');
optionDiv.textContent = option.text;
optionDiv.addEventListener('click', () => {
select.value = option.value;
div.textContent = option.text;
itemsDiv.style.display = 'none';
});
optionsContainer.appendChild(optionDiv);
});
itemsDiv.appendChild(optionsContainer);
// Add search functionality
searchInput.addEventListener('input', (e) => {
const searchText = e.target.value.toLowerCase();
Array.from(optionsContainer.children).forEach(optionDiv => {
const text = optionDiv.textContent.toLowerCase();
optionDiv.classList.toggle('select-option-hidden', !text.includes(searchText));
});
});
// Prevent dropdown from closing when clicking search
searchInput.addEventListener('click', (e) => {
e.stopPropagation();
});
select.parentElement.appendChild(itemsDiv);
div.addEventListener('click', (e) => {
e.stopPropagation();
closeAllSelect(itemsDiv);
itemsDiv.style.display = itemsDiv.style.display === 'block' ? 'none' : 'block';
if (itemsDiv.style.display === 'block') {
searchInput.focus();
searchInput.value = '';
// Show all options when opening dropdown
Array.from(optionsContainer.children).forEach(optionDiv => {
optionDiv.classList.remove('select-option-hidden');
});
}
});
});
document.addEventListener('click', () => closeAllSelect(null));
}
function closeAllSelect(elmnt) {
document.querySelectorAll('.select-items').forEach(item => {
if (item !== elmnt) item.style.display = 'none';
});
}
const dropZone = document.getElementById('dropZone');
const imageUpload = document.getElementById('imageUpload');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => dropZone.classList.add('drag-over'));
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, () => dropZone.classList.remove('drag-over'));
});
dropZone.addEventListener('drop', handleDrop);
dropZone.addEventListener('click', () => imageUpload.click());
function handleDrop(e) {
const dt = e.dataTransfer;
const file = dt.files[0];
handleFile(file);
}
document.getElementById('imageUpload').addEventListener('change', function (e) {
const file = e.target.files[0];
if (file) handleFile(file);
});
function handleFile(file) {
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
document.getElementById('preview').src = e.target.result;
document.getElementById('imagePreview').classList.remove('hidden');
document.getElementById('dropText').classList.add('hidden');
}
reader.readAsDataURL(file);
}
}
document.addEventListener('DOMContentLoaded', () => {
initCustomSelects();
// Add examples data
window.examples = `{{ productsString | safe }}`;
window.examples = JSON.parse(window.examples);
});
async function loadExample(index) {
index = parseInt(index);
const example = window.examples[index];
// Set form values
document.getElementById('brand').value = example.brand;
document.getElementById('color').value = example.color;
document.getElementById('gender').value = example.gender;
document.getElementById('midsole').value = example.midsole;
document.getElementById('upperMaterial').value = example.upperMaterial;
// Update custom select displays
document.querySelectorAll('.select-selected').forEach(div => {
const select = div.previousElementSibling;
div.textContent = select.options[select.selectedIndex].text;
});
// Load and display image
const response = await fetch(example.img);
const blob = await response.blob();
const file = new File([blob], 'example.png', { type: 'image/png' });
// Trigger file handler
handleFile(file);
// Update the hidden file input
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
document.getElementById('imageUpload').files = dataTransfer.files;
}
async function predict() {
const imageFile = document.getElementById('imageUpload').files[0];
if (!imageFile) {
alert('Please select an image');
return;
}
const reader = new FileReader();
reader.onload = async function (e) {
const base64Image = e.target.result.split(',')[1];
const data = {
image: base64Image,
brand: document.getElementById('brand').value,
color: document.getElementById('color').value,
gender: document.getElementById('gender').value,
midsole: document.getElementById('midsole').value,
upperMaterial: document.getElementById('upperMaterial').value
};
try {
const response = await fetch('/predict', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.error) {
alert('Error: ' + result.error);
} else {
document.getElementById('result').classList.remove('hidden');
const predictionsContainer = document.getElementById('predictions');
predictionsContainer.innerHTML = '';
// Sort categories by confidence
const predictions = result.categories.map((category, index) => ({
category,
confidence: result.confidence[index]
})).sort((a, b) => b.confidence - a.confidence);
predictions.forEach(({ category, confidence }) => {
const confidencePercent = (confidence * 100).toFixed(2);
const predictionHtml = `
<div class="gradient-border p-4">
<div class="flex items-center justify-between">
<p>Category: <span class="font-semibold text-blue-400">${category}</span></p>
<p>Confidence: <span class="font-semibold text-blue-400">${confidencePercent}</span>%</p>
</div>
<div class="w-full bg-gray-700/30 rounded-full h-4 mt-2">
<div class="bg-gradient-to-r from-blue-500 to-purple-500 h-4 rounded-full transition-all duration-500"
style="width: ${confidencePercent}%"></div>
</div>
</div>`;
predictionsContainer.innerHTML += predictionHtml;
});
}
} catch (error) {
alert('Error: ' + error.message);
}
};
reader.readAsDataURL(imageFile);
}
</script>
</body>
</html>