Darak / front /design.html
Antigravity AI
Fix hardcoded localhost URLs to use relative paths for Hugging Face deployment
3f5ff7f
Raw
History Blame Contribute Delete
24.5 kB
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Real Estate - Studio</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['Cairo', 'sans-serif'] },
colors: {
dark: { bg: '#0B0F19', surface: '#111827', accent: '#F59E0B' }
}
}
}
}
</script>
<style>
body { background-color: #0B0F19; color: #FFFFFF; }
/* Custom Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0B0F19; }
::-webkit-scrollbar-thumb { background: #374151; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #F59E0B; }
.upload-zone {
border: 2px dashed rgba(245, 158, 11, 0.4);
transition: all 0.3s;
background: rgba(0, 0, 0, 0.2);
}
.upload-zone:hover {
border-color: #F59E0B;
background: rgba(245, 158, 11, 0.05);
}
/* Slider */
.slider-container {
position: relative;
width: 100%;
height: 60vh;
overflow: hidden;
border-radius: 1rem;
user-select: none;
touch-action: none;
box-shadow: 0 10px 40px rgba(0,0,0,0.8);
}
.slider-img {
position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;
}
.slider-after-container {
position: absolute; top: 0; left: 0; width: 50%; height: 100%; overflow: hidden;
border-right: 3px solid #F59E0B; z-index: 10;
}
.slider-handle {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 40px; height: 40px; background: #F59E0B; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
cursor: ew-resize; z-index: 20; color: #000; box-shadow: 0 0 15px rgba(0,0,0,0.5);
}
/* Scanning effect */
.scan-line {
position: absolute; top: 0; left: 0; width: 100%; height: 5px;
background: #F59E0B; box-shadow: 0 0 15px #F59E0B, 0 0 30px #FCD34D;
z-index: 50; animation: scanAnim 3s ease-in-out infinite; opacity: 0.8;
}
@keyframes scanAnim {
0% { top: 0; }
50% { top: 100%; }
100% { top: 0; }
}
.scan-overlay {
position: absolute; inset: 0; background: rgba(245, 158, 11, 0.1); z-index: 40;
}
.btn-glow:hover { box-shadow: 0 0 20px rgba(245, 158, 11, 0.4); }
</style>
</head>
<body class="h-screen flex flex-col overflow-hidden">
<!-- Navbar -->
<nav class="flex items-center px-6 py-4 border-b border-white/10 bg-dark-bg z-50">
<div class="flex items-center gap-3 cursor-pointer" onclick="window.location.href='index.html'">
<img src="logo.png" alt="Dark Logo" class="h-8 object-contain">
</div>
<div class="mr-auto text-sm font-bold text-gray-400 bg-dark-surface px-4 py-2 rounded-full border border-white/5">
<i class="fa-solid fa-layer-group text-dark-accent mr-1"></i> استوديو التصميم
</div>
</nav>
<!-- Split Screen Main -->
<main class="flex-1 flex overflow-hidden">
<!-- RIGHT PANEL (Controls - Arabic RTL so it's on the right side visually) -->
<aside class="w-full md:w-80 lg:w-96 bg-dark-surface border-l border-white/10 flex flex-col z-20 shadow-2xl">
<div class="flex-1 overflow-y-auto p-6 space-y-8">
<!-- Upload -->
<div>
<h3 class="text-lg font-bold mb-3"><i class="fa-solid fa-image text-dark-accent ml-2"></i>صورة الغرفة</h3>
<div id="dropzone" class="upload-zone rounded-xl h-40 flex flex-col items-center justify-center cursor-pointer relative overflow-hidden group">
<input type="file" id="file-input" class="hidden" accept="image/png, image/jpeg, image/jpg">
<i class="fa-solid fa-cloud-arrow-up text-3xl text-gray-500 mb-2 group-hover:text-dark-accent transition"></i>
<p class="font-bold text-sm">اسحب الصورة أو اضغط هنا</p>
<!-- Mini Preview overlay -->
<div id="image-preview" class="absolute inset-0 bg-black hidden flex-col items-center justify-center group">
<img id="preview-img" src="" class="w-full h-full object-cover opacity-50">
<button id="remove-img-btn" class="absolute bg-red-500 text-white w-8 h-8 rounded-full z-10 hover:scale-110 transition"><i class="fa-solid fa-xmark"></i></button>
</div>
</div>
</div>
<!-- Style Selection -->
<div>
<h3 class="text-lg font-bold mb-3"><i class="fa-solid fa-palette text-dark-accent ml-2"></i>النمط المطلوب</h3>
<div class="grid grid-cols-2 gap-2">
<button class="style-btn relative overflow-hidden bg-black border-2 border-dark-accent h-24 rounded-lg font-bold transition flex flex-col items-center justify-center gap-1 group">
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=200')] bg-cover opacity-30"></div>
<span class="relative z-10 text-dark-accent text-sm shadow-black drop-shadow-md">مودرن</span>
</button>
<button class="style-btn relative overflow-hidden bg-black border-2 border-transparent hover:border-white/20 h-24 rounded-lg font-bold transition flex flex-col items-center justify-center gap-1 group text-gray-400">
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1540932239986-30128078f3c5?w=200')] bg-cover opacity-20"></div>
<span class="relative z-10 text-sm shadow-black drop-shadow-md">كلاسيك</span>
</button>
<button class="style-btn relative overflow-hidden bg-black border-2 border-transparent hover:border-white/20 h-24 rounded-lg font-bold transition flex flex-col items-center justify-center gap-1 group text-gray-400">
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=200')] bg-cover opacity-20"></div>
<span class="relative z-10 text-sm shadow-black drop-shadow-md">بوهيمي</span>
</button>
<button class="style-btn relative overflow-hidden bg-black border-2 border-transparent hover:border-white/20 h-24 rounded-lg font-bold transition flex flex-col items-center justify-center gap-1 group text-gray-400">
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=200')] bg-cover opacity-20"></div>
<span class="relative z-10 text-sm shadow-black drop-shadow-md">مينيمال</span>
</button>
</div>
</div>
<!-- Instructions -->
<div>
<h3 class="text-lg font-bold mb-3"><i class="fa-solid fa-pen-nib text-dark-accent ml-2"></i>تعليمات المولد</h3>
<textarea id="extra-instructions" class="w-full bg-black border border-white/10 rounded-lg p-3 text-sm text-white focus:border-dark-accent outline-none h-24 resize-none" placeholder="أريد إضافة مكتب زجاجي ونباتات داخلية..."></textarea>
</div>
</div>
<!-- Generate Action -->
<div class="p-6 bg-dark-surface border-t border-white/10">
<button id="generate-btn" class="w-full bg-dark-accent text-black font-black py-4 rounded-xl flex items-center justify-center gap-2 btn-glow transition">
<i class="fa-solid fa-bolt"></i> توليد التصميم
</button>
</div>
</aside>
<!-- LEFT PANEL (Canvas Viewer) -->
<section class="flex-1 bg-[#05080f] relative flex flex-col overflow-y-auto">
<!-- Canvas Container -->
<div class="p-8 w-full max-w-5xl mx-auto flex-1 flex flex-col">
<!-- Empty/Placeholder State -->
<div id="canvas-empty" class="flex-1 flex flex-col items-center justify-center text-gray-600">
<i class="fa-solid fa-border-none text-6xl mb-4 opacity-50"></i>
<p class="text-xl font-bold">مساحة العمل فارغة</p>
<p class="text-sm">قم برفع صورة للبدء في المعاينة الحية</p>
</div>
<!-- Processing State (Scanning) -->
<div id="canvas-processing" class="hidden flex-1 relative rounded-2xl overflow-hidden border border-white/10">
<img id="processing-img" src="" class="w-full h-full object-contain bg-black">
<div class="scan-overlay"></div>
<div class="scan-line"></div>
<div class="absolute bottom-10 left-1/2 -translate-x-1/2 bg-black/80 backdrop-blur-md px-6 py-3 rounded-full border border-dark-accent/50 text-dark-accent font-bold flex items-center gap-3">
<i class="fa-solid fa-circle-notch fa-spin"></i> <span id="processing-text">يتم تحليل الغرفة...</span>
</div>
</div>
<!-- Results State -->
<div id="canvas-results" class="hidden flex-col w-full">
<div class="flex items-center justify-between mb-4">
<h2 class="text-2xl font-black text-white"><i class="fa-solid fa-wand-magic-sparkles text-dark-accent ml-2"></i>النتيجة النهائية</h2>
<span id="result-desc" class="text-gray-400 text-sm"></span>
</div>
<!-- Slider -->
<div class="slider-container mb-8" id="comparison-slider">
<!-- Before -->
<img id="result-before-img" src="" class="slider-img grayscale" alt="Before">
<!-- After -->
<div class="slider-after-container" id="slider-overlay">
<img id="result-after-img" src="" class="slider-img" style="width: 200%; max-width: none;" alt="After">
</div>
<!-- Handle -->
<div class="slider-handle" id="slider-handle"><i class="fa-solid fa-arrows-left-right"></i></div>
<div class="absolute top-4 right-4 bg-black/80 text-white px-2 py-1 rounded text-xs z-10 pointer-events-none">قبل</div>
<div class="absolute top-4 left-4 bg-dark-accent text-black px-2 py-1 rounded text-xs font-bold z-20 pointer-events-none">بعد</div>
</div>
<!-- Furniture -->
<h3 class="text-xl font-bold mb-4 border-b border-white/10 pb-2">عناصر التصميم (الأثاث المقترح)</h3>
<div id="furniture-grid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 pb-12">
<!-- Populated by JS -->
</div>
</div>
</div>
</section>
</main>
<script>
document.addEventListener('DOMContentLoaded', () => {
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const preview = document.getElementById('image-preview');
const previewImg = document.getElementById('preview-img');
const removeImgBtn = document.getElementById('remove-img-btn');
const styleBtns = document.querySelectorAll('.style-btn');
const extraInstructions = document.getElementById('extra-instructions');
const generateBtn = document.getElementById('generate-btn');
const canvasEmpty = document.getElementById('canvas-empty');
const canvasProcessing = document.getElementById('canvas-processing');
const canvasResults = document.getElementById('canvas-results');
const processingImg = document.getElementById('processing-img');
const processingText = document.getElementById('processing-text');
let isImageUploaded = false;
let selectedStyle = "مودرن";
let base64Image = null;
// 1. Upload Logic
dropzone.addEventListener('click', (e) => {
if(!isImageUploaded && e.target !== fileInput) fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if(file) {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const MAX = 1000;
let w = img.width, h = img.height;
if (w > h && w > MAX) { h *= MAX / w; w = MAX; }
else if (h > MAX) { w *= MAX / h; h = MAX; }
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
base64Image = canvas.toDataURL('image/jpeg', 0.8);
previewImg.src = base64Image;
preview.classList.remove('hidden');
preview.classList.add('flex');
isImageUploaded = true;
// Show in canvas right away
canvasEmpty.classList.add('hidden');
canvasProcessing.classList.remove('hidden');
canvasProcessing.querySelector('.scan-line').style.display = 'none';
canvasProcessing.querySelector('.scan-overlay').style.display = 'none';
canvasProcessing.querySelector('.absolute.bottom-10').style.display = 'none';
processingImg.src = base64Image;
canvasResults.classList.add('hidden');
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
removeImgBtn.addEventListener('click', (e) => {
e.stopPropagation();
preview.classList.add('hidden');
preview.classList.remove('flex');
isImageUploaded = false;
fileInput.value = '';
base64Image = null;
canvasEmpty.classList.remove('hidden');
canvasProcessing.classList.add('hidden');
canvasResults.classList.add('hidden');
});
// 2. Style Logic
styleBtns.forEach(btn => {
btn.addEventListener('click', () => {
styleBtns.forEach(b => {
b.classList.remove('border-dark-accent');
b.classList.add('border-transparent', 'text-gray-400');
b.querySelector('span').classList.remove('text-dark-accent');
});
btn.classList.add('border-dark-accent');
btn.classList.remove('border-transparent', 'text-gray-400');
btn.querySelector('span').classList.add('text-dark-accent');
selectedStyle = btn.querySelector('span').innerText.trim();
});
});
// 3. Generate Logic
generateBtn.addEventListener('click', async () => {
if(!isImageUploaded) { alert('الرجاء رفع صورة الغرفة أولاً'); return; }
// UI to Processing State
canvasEmpty.classList.add('hidden');
canvasResults.classList.add('hidden');
canvasProcessing.classList.remove('hidden');
canvasProcessing.querySelector('.scan-line').style.display = 'block';
canvasProcessing.querySelector('.scan-overlay').style.display = 'block';
canvasProcessing.querySelector('.absolute.bottom-10').style.display = 'flex';
generateBtn.disabled = true;
generateBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> جاري المعالجة...';
// Sequence texts
const texts = ["جاري تحليل المساحة...", "استخراج أبعاد الغرفة...", `تطبيق نمط ${selectedStyle}...`, "البحث عن الأثاث المناسب..."];
let txtIdx = 0;
const txtInt = setInterval(() => {
txtIdx = (txtIdx + 1) % texts.length;
processingText.innerText = texts[txtIdx];
}, 2000);
try {
const response = await fetch('/api/design/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
image_base64: base64Image,
style: selectedStyle,
instructions: extraInstructions.value
})
});
const result = await response.json();
clearInterval(txtInt);
if (result && result.success && result.data) {
const data = result.data;
document.getElementById('result-before-img').src = base64Image;
// Use the mock image or generated image url (mocking for now as API returns placeholder)
document.getElementById('result-after-img').src = "https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=1200";
if(data.design_description) document.getElementById('result-desc').innerText = data.design_description;
const furnGrid = document.getElementById('furniture-grid');
furnGrid.innerHTML = '';
const icons = {"sofa": "fa-couch", "coffee table": "fa-table", "lamp": "fa-lightbulb", "rug": "fa-map", "art": "fa-palette", "chair": "fa-chair"};
if(data.furniture) {
data.furniture.forEach(item => {
const fType = (item.furniture_type || '').toLowerCase();
const defaultIcon = icons[fType] || 'fa-box';
furnGrid.innerHTML += `
<div class="bg-dark-surface border border-white/5 rounded-xl p-4 hover:border-dark-accent/50 transition cursor-pointer flex flex-col">
<div class="h-24 bg-[#05080f] rounded-lg mb-3 flex items-center justify-center text-3xl text-gray-600">
<i class="fa-solid ${defaultIcon}"></i>
</div>
<div class="text-[10px] text-dark-accent font-bold mb-1">${item.category || ''}</div>
<h4 class="font-bold text-white text-sm mb-2 leading-tight flex-1">${item.name || 'قطعة أثاث'}</h4>
<div class="flex justify-between items-center mt-2 border-t border-white/5 pt-2">
<span class="font-black text-sm">${item.price ? item.price + ' ج' : ''}</span>
<button class="text-gray-400 hover:text-dark-accent transition"><i class="fa-solid fa-cart-shopping"></i></button>
</div>
</div>
`;
});
}
// Show Results
canvasProcessing.classList.add('hidden');
canvasResults.classList.remove('hidden');
canvasResults.classList.add('flex');
initSlider();
} else {
alert('حدث خطأ في استخراج التصميم.');
canvasProcessing.classList.add('hidden');
canvasEmpty.classList.remove('hidden');
}
} catch (err) {
clearInterval(txtInt);
alert('تعذر الاتصال بالخادم.');
canvasProcessing.classList.add('hidden');
canvasEmpty.classList.remove('hidden');
} finally {
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa-solid fa-bolt"></i> توليد التصميم';
}
});
// 4. Slider Logic
function initSlider() {
const sliderContainer = document.getElementById('comparison-slider');
const overlay = document.getElementById('slider-overlay');
const handle = document.getElementById('slider-handle');
const afterImg = document.getElementById('result-after-img');
let isSliding = false;
const updateImageSize = () => { afterImg.style.width = sliderContainer.offsetWidth + 'px'; };
window.addEventListener('resize', updateImageSize);
updateImageSize();
// reset to middle
overlay.style.width = "50%";
handle.style.left = "50%";
const slide = (x) => {
const rect = sliderContainer.getBoundingClientRect();
let position = ((x - rect.left) / rect.width) * 100;
if(position < 0) position = 0; if(position > 100) position = 100;
overlay.style.width = position + "%";
handle.style.left = position + "%";
};
sliderContainer.addEventListener('mousedown', (e) => { isSliding = true; slide(e.clientX); });
sliderContainer.addEventListener('mousemove', (e) => { if(isSliding) slide(e.clientX); });
window.addEventListener('mouseup', () => { isSliding = false; });
sliderContainer.addEventListener('touchstart', (e) => { isSliding = true; slide(e.touches[0].clientX); });
sliderContainer.addEventListener('touchmove', (e) => { if(isSliding) slide(e.touches[0].clientX); });
window.addEventListener('touchend', () => { isSliding = false; });
}
});
</script>
</body>
</html>