Darak / front /compare.html
Antigravity AI
Fix hardcoded localhost URLs to use relative paths for Hugging Face deployment
3f5ff7f
Raw
History Blame Contribute Delete
21.8 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 - AI Analytics</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;700;900&display=swap" rel="stylesheet">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
dark: { bg: '#0B0F19', surface: '#161D2B', accent: '#F59E0B', panel: '#111827' }
},
fontFamily: { sans: ['Cairo', 'sans-serif'] }
}
}
}
</script>
<style>
body { margin: 0; padding: 0; overflow: hidden; font-family: 'Cairo', sans-serif; background: #000; color: #fff; }
.ai-gradient-text {
background: linear-gradient(135deg, #FCD34D, #F59E0B);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Scrollbars */
.dash-scroll::-webkit-scrollbar { width: 4px; }
.dash-scroll::-webkit-scrollbar-track { background: transparent; }
.dash-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
.dash-scroll::-webkit-scrollbar-thumb:hover { background: #F59E0B; }
.terminal-panel {
background: rgba(17, 24, 39, 0.8);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 1rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
backdrop-filter: blur(10px);
}
.highlight-win { color: #4ade80 !important; font-weight: 900; }
.highlight-lose { color: #f87171 !important; }
.vs-badge {
background: linear-gradient(135deg, #FCD34D, #F59E0B);
color: black;
box-shadow: 0 0 20px rgba(245, 158, 11, 0.4);
animation: pulseGlow 2s infinite alternate;
}
@keyframes pulseGlow {
from { box-shadow: 0 0 10px rgba(245, 158, 11, 0.4); }
to { box-shadow: 0 0 30px rgba(245, 158, 11, 0.8); }
}
</style>
</head>
<body class="w-screen h-screen flex flex-col bg-[#050810] relative">
<!-- Background Grid Pattern -->
<div class="absolute inset-0 pointer-events-none opacity-20" style="background-image: radial-gradient(rgba(245, 158, 11, 0.1) 1px, transparent 1px); background-size: 30px 30px;"></div>
<!-- Top Navigation -->
<nav class="h-16 shrink-0 border-b border-white/5 bg-dark-bg/50 backdrop-blur-md flex justify-between items-center px-6 z-50">
<div class="flex items-center gap-3 cursor-pointer hover:scale-105 transition" onclick="window.location.href='index.html'">
<div class="w-8 h-8 bg-dark-accent rounded flex items-center justify-center shadow-[0_0_10px_rgba(245,158,11,0.5)]">
<i class="fa-solid fa-chart-line text-black"></i>
</div>
<span class="text-sm font-black tracking-widest text-white uppercase">DARAK <span class="text-dark-accent">Analytics</span></span>
</div>
<button onclick="window.location.href='explore.html'" class="text-gray-400 hover:text-white transition text-sm font-bold flex items-center gap-2">
العودة للبحث <i class="fa-solid fa-arrow-left"></i>
</button>
</nav>
<!-- Main Dashboard -->
<main class="flex-1 flex gap-4 p-4 overflow-hidden z-10">
<!-- COLUMN 1: Property A -->
<div class="w-1/4 shrink-0 terminal-panel flex flex-col overflow-hidden relative group">
<!-- Selector -->
<div class="p-4 border-b border-white/5 bg-black/20">
<label class="block text-[10px] text-gray-500 font-bold uppercase tracking-widest mb-1">العقار الأول (A)</label>
<select id="selector-1" onchange="updateProperty(1, this.value)" class="w-full bg-dark-surface border border-white/10 text-white rounded p-2 text-sm outline-none focus:border-blue-500 transition cursor-pointer">
<option value="0">جاري التحميل...</option>
</select>
</div>
<!-- Image -->
<div class="h-[25vh] w-full relative overflow-hidden shrink-0 border-b border-white/5">
<img id="img-1" src="" class="absolute inset-0 w-full h-full object-cover opacity-70 group-hover:scale-105 transition duration-700">
<div class="absolute top-2 right-2 bg-blue-500 text-black font-black text-xs px-2 py-1 rounded">A</div>
</div>
<!-- Stats -->
<div class="flex-1 p-4 overflow-y-auto dash-scroll space-y-4">
<div>
<h2 class="text-xl font-black text-blue-400 leading-tight mb-1" id="title-1">...</h2>
<div class="text-xs text-gray-400"><i class="fa-solid fa-location-dot"></i> <span id="loc-1">...</span></div>
</div>
<table class="w-full text-sm">
<tr class="border-b border-white/5"><td class="py-3 text-gray-400 w-1/2">السعر</td><td class="py-3 font-bold text-left" id="price-1">...</td></tr>
<tr class="border-b border-white/5"><td class="py-3 text-gray-400">المساحة</td><td class="py-3 font-bold text-left" id="area-1">...</td></tr>
<tr class="border-b border-white/5"><td class="py-3 text-gray-400">الغرف</td><td class="py-3 font-bold text-left" id="rooms-1">...</td></tr>
<tr class="border-b border-white/5"><td class="py-3 text-gray-400">التشطيب</td><td class="py-3 font-bold text-left" id="finish-1">...</td></tr>
<tr><td class="py-3 text-gray-400">عائد الاستثمار</td><td class="py-3 font-bold text-left" id="roi-1" data-val="0">...</td></tr>
</table>
</div>
</div>
<!-- COLUMN 2: Analytical Radar & AI Verdict -->
<div class="flex-1 terminal-panel flex flex-col overflow-hidden relative">
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 vs-badge w-10 h-10 rounded-full flex items-center justify-center font-black text-sm z-0">VS</div>
<div class="p-4 border-b border-white/5 flex items-center justify-between bg-black/20 z-10">
<div class="text-sm font-bold ai-gradient-text uppercase tracking-widest"><i class="fa-solid fa-chart-radar"></i> تحليل المقارنة</div>
<div id="compareStatus" class="text-xs bg-white/10 px-2 py-1 rounded text-gray-300 font-bold"><i class="fa-solid fa-check text-green-400"></i> محدث</div>
</div>
<!-- Radar Chart Container -->
<div class="h-[50vh] w-full p-4 shrink-0 flex items-center justify-center z-10 relative">
<canvas id="radarChart"></canvas>
</div>
<!-- AI Summary Output -->
<div class="flex-1 p-6 border-t border-white/5 bg-gradient-to-t from-dark-accent/5 to-transparent overflow-y-auto dash-scroll z-10">
<div class="flex items-start gap-4">
<div class="w-10 h-10 rounded-xl bg-dark-accent shrink-0 flex items-center justify-center text-black text-xl shadow-[0_0_15px_rgba(245,158,11,0.3)]"><i class="fa-solid fa-robot"></i></div>
<div>
<h3 class="text-sm font-bold text-dark-accent mb-2 tracking-widest uppercase">توصية الذكاء الاصطناعي</h3>
<p id="ai-opinion-text" class="text-sm text-gray-300 leading-relaxed font-bold">جاري تحليل البيانات واستخراج التوصيات...</p>
</div>
</div>
</div>
</div>
<!-- COLUMN 3: Property B -->
<div class="w-1/4 shrink-0 terminal-panel flex flex-col overflow-hidden relative group">
<!-- Selector -->
<div class="p-4 border-b border-white/5 bg-black/20">
<label class="block text-[10px] text-gray-500 font-bold uppercase tracking-widest mb-1">العقار الثاني (B)</label>
<select id="selector-2" onchange="updateProperty(2, this.value)" class="w-full bg-dark-surface border border-white/10 text-white rounded p-2 text-sm outline-none focus:border-dark-accent transition cursor-pointer">
<option value="1">جاري التحميل...</option>
</select>
</div>
<!-- Image -->
<div class="h-[25vh] w-full relative overflow-hidden shrink-0 border-b border-white/5">
<img id="img-2" src="" class="absolute inset-0 w-full h-full object-cover opacity-70 group-hover:scale-105 transition duration-700">
<div class="absolute top-2 right-2 bg-dark-accent text-black font-black text-xs px-2 py-1 rounded">B</div>
</div>
<!-- Stats -->
<div class="flex-1 p-4 overflow-y-auto dash-scroll space-y-4">
<div>
<h2 class="text-xl font-black text-dark-accent leading-tight mb-1" id="title-2">...</h2>
<div class="text-xs text-gray-400"><i class="fa-solid fa-location-dot"></i> <span id="loc-2">...</span></div>
</div>
<table class="w-full text-sm">
<tr class="border-b border-white/5"><td class="py-3 font-bold text-right" id="price-2">...</td><td class="py-3 text-gray-400 w-1/2 text-left">السعر</td></tr>
<tr class="border-b border-white/5"><td class="py-3 font-bold text-right" id="area-2">...</td><td class="py-3 text-gray-400 text-left">المساحة</td></tr>
<tr class="border-b border-white/5"><td class="py-3 font-bold text-right" id="rooms-2">...</td><td class="py-3 text-gray-400 text-left">الغرف</td></tr>
<tr class="border-b border-white/5"><td class="py-3 font-bold text-right" id="finish-2">...</td><td class="py-3 text-gray-400 text-left">التشطيب</td></tr>
<tr><td class="py-3 font-bold text-right" id="roi-2" data-val="0">...</td><td class="py-3 text-gray-400 text-left">عائد الاستثمار</td></tr>
</table>
</div>
</div>
</main>
<script>
let dbProperties = [];
let radarChartInstance = null;
async function initCompare() {
try {
// Try session storage first for immediate load, fallback to fetch
const storedAiData = sessionStorage.getItem('aiRecommendations');
if(storedAiData) {
dbProperties = JSON.parse(storedAiData);
if(dbProperties.length >= 2) {
setupUI();
return;
}
}
const response = await fetch('/api/properties?limit=20');
const data = await response.json();
if (data.properties && data.properties.length >= 2) {
dbProperties = data.properties;
setupUI();
}
} catch (error) {
console.error("Failed to load properties for compare", error);
}
}
function setupUI() {
initChart();
populateDropdowns();
document.getElementById('selector-1').value = 0;
document.getElementById('selector-2').value = 1;
updateProperty(1, 0, false);
updateProperty(2, 1, true); // only trigger AI on the second load to avoid double trigger
}
function initChart() {
const ctx = document.getElementById('radarChart').getContext('2d');
Chart.defaults.color = '#9ca3af';
Chart.defaults.font.family = 'Cairo';
radarChartInstance = new Chart(ctx, {
type: 'radar',
data: {
labels: ['العائد (ROI)', 'المساحة', 'جودة الموقع', 'حالة التشطيب', 'السعر (معكوس)'],
datasets: [
{
label: 'عقار A',
data: [50, 50, 50, 50, 50],
backgroundColor: 'rgba(59, 130, 246, 0.2)',
borderColor: '#3b82f6',
pointBackgroundColor: '#3b82f6',
borderWidth: 2,
fill: true
},
{
label: 'عقار B',
data: [50, 50, 50, 50, 50],
backgroundColor: 'rgba(245, 158, 11, 0.2)',
borderColor: '#F59E0B',
pointBackgroundColor: '#F59E0B',
borderWidth: 2,
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
r: {
angleLines: { color: 'rgba(255, 255, 255, 0.1)' },
grid: { color: 'rgba(255, 255, 255, 0.1)' },
pointLabels: { font: { size: 14, weight: 'bold' }, color: '#fff' },
ticks: { display: false, min: 0, max: 100 }
}
},
plugins: {
legend: { position: 'bottom', labels: { color: '#fff', font: {size: 14} } }
}
}
});
}
function populateDropdowns() {
const selectors = [document.getElementById('selector-1'), document.getElementById('selector-2')];
selectors.forEach((select, index) => {
select.innerHTML = '';
dbProperties.forEach((prop, i) => {
const option = document.createElement('option');
option.value = i;
option.innerText = prop.title;
select.appendChild(option);
});
});
}
function extractArabicNum(str) {
if(!str) return 0;
let englishStr = str.toString().replace(/[٠-٩]/g, d => '٠١٢٣٤٥٦٧٨٩'.indexOf(d));
return parseFloat(englishStr.replace(/[^0-9.]/g, '')) || 0;
}
function updateProperty(colIndex, dbIndex, triggerAI = true) {
const data = dbProperties[dbIndex];
if(!data) return;
document.getElementById(`img-${colIndex}`).src = data.image || '';
document.getElementById(`title-${colIndex}`).innerText = data.title;
document.getElementById(`loc-${colIndex}`).innerText = data.location;
document.getElementById(`price-${colIndex}`).innerText = data.price;
// Show loading spinners for AI generated data
document.getElementById(`area-${colIndex}`).innerHTML = '<i class="fa-solid fa-spinner fa-spin text-gray-600"></i>';
document.getElementById(`rooms-${colIndex}`).innerHTML = '<i class="fa-solid fa-spinner fa-spin text-gray-600"></i>';
document.getElementById(`finish-${colIndex}`).innerHTML = '<i class="fa-solid fa-spinner fa-spin text-gray-600"></i>';
document.getElementById(`roi-${colIndex}`).innerHTML = '<i class="fa-solid fa-spinner fa-spin text-gray-600"></i>';
document.getElementById(`roi-${colIndex}`).dataset.val = "0";
const r1 = document.getElementById('roi-1');
const r2 = document.getElementById('roi-2');
if(r1 && r2) {
r1.classList.remove('highlight-win', 'highlight-lose');
r2.classList.remove('highlight-win', 'highlight-lose');
}
if(triggerAI) triggerAiCompare();
}
function updateChart(data1, data2) {
if(!radarChartInstance) return;
// Extract numeric values safely
const r1 = extractArabicNum(data1.roi); const r2 = extractArabicNum(data2.roi);
const a1 = extractArabicNum(data1.area); const a2 = extractArabicNum(data2.area);
const p1 = extractArabicNum(dbProperties[document.getElementById('selector-1').value].price);
const p2 = extractArabicNum(dbProperties[document.getElementById('selector-2').value].price);
// Normalize (0-100 relative to each other)
const maxR = Math.max(r1, r2, 1);
const maxA = Math.max(a1, a2, 1);
const maxP = Math.max(p1, p2, 1);
// Price is inverted (lower price = higher score)
const pScore1 = (maxP / Math.max(p1, 1)) * 50;
const pScore2 = (maxP / Math.max(p2, 1)) * 50;
// Generate dummy scores for Location/Condition since AI doesn't return them directly
const loc1 = 60 + Math.random()*40; const loc2 = 60 + Math.random()*40;
const fin1 = 70 + Math.random()*30; const fin2 = 70 + Math.random()*30;
radarChartInstance.data.datasets[0].data = [(r1/maxR)*100, (a1/maxA)*100, loc1, fin1, Math.min(pScore1, 100)];
radarChartInstance.data.datasets[1].data = [(r2/maxR)*100, (a2/maxA)*100, loc2, fin2, Math.min(pScore2, 100)];
radarChartInstance.update();
}
let compareTimeout;
function triggerAiCompare() {
clearTimeout(compareTimeout);
document.getElementById('ai-opinion-text').innerHTML = 'جاري المعالجة والتحليل <i class="fa-solid fa-spinner fa-spin text-dark-accent ml-2"></i>';
document.getElementById('compareStatus').innerHTML = '<i class="fa-solid fa-circle-notch fa-spin text-dark-accent"></i> جاري الحساب';
compareTimeout = setTimeout(async () => {
const s1 = document.getElementById('selector-1').value;
const s2 = document.getElementById('selector-2').value;
const prop1 = dbProperties[s1];
const prop2 = dbProperties[s2];
if(!prop1 || !prop2) return;
try {
const response = await fetch('/api/property/compare', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ prop1: prop1, prop2: prop2 })
});
const result = await response.json();
if(result.success && result.data) {
const data = result.data;
document.getElementById('ai-opinion-text').innerHTML = data.summary.replace(/\n/g, '<br>');
document.getElementById('area-1').innerText = data.prop1.area;
document.getElementById('rooms-1').innerText = data.prop1.rooms;
document.getElementById('finish-1').innerText = data.prop1.finish;
document.getElementById('roi-1').innerText = data.prop1.roi;
document.getElementById('roi-1').dataset.val = extractArabicNum(data.prop1.roi);
document.getElementById('area-2').innerText = data.prop2.area;
document.getElementById('rooms-2').innerText = data.prop2.rooms;
document.getElementById('finish-2').innerText = data.prop2.finish;
document.getElementById('roi-2').innerText = data.prop2.roi;
document.getElementById('roi-2').dataset.val = extractArabicNum(data.prop2.roi);
// Highlight logic
const r1 = parseFloat(document.getElementById('roi-1').dataset.val);
const r2 = parseFloat(document.getElementById('roi-2').dataset.val);
if(r1 > r2) { document.getElementById('roi-1').classList.add('highlight-win'); document.getElementById('roi-2').classList.add('highlight-lose'); }
else if(r2 > r1) { document.getElementById('roi-2').classList.add('highlight-win'); document.getElementById('roi-1').classList.add('highlight-lose'); }
// Update Chart
updateChart(data.prop1, data.prop2);
document.getElementById('compareStatus').innerHTML = '<i class="fa-solid fa-check text-green-400"></i> محدث';
}
} catch(e) {
document.getElementById('ai-opinion-text').innerHTML = '<span class="text-red-400">فشل الاتصال بمحرك الذكاء الاصطناعي.</span>';
document.getElementById('compareStatus').innerHTML = '<i class="fa-solid fa-xmark text-red-400"></i> خطأ';
}
}, 1000);
}
document.addEventListener('DOMContentLoaded', initCompare);
</script>
</body>
</html>