Darak / front /property.html
Antigravity AI
Fix hardcoded localhost URLs to use relative paths for Hugging Face deployment
3f5ff7f
Raw
History Blame Contribute Delete
19.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 - 3D Showroom</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">
<!-- Leaflet Map -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
dark: { bg: '#0B0F19', surface: '#161D2B', accent: '#F59E0B' }
},
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;
}
/* Showroom Drawer */
.showroom-drawer {
background: rgba(11, 15, 25, 0.85);
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 -20px 60px rgba(0,0,0,0.6);
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Scrollbars */
.drawer-scroll::-webkit-scrollbar { width: 4px; height: 4px; }
.drawer-scroll::-webkit-scrollbar-track { background: transparent; }
.drawer-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 10px; }
.drawer-scroll::-webkit-scrollbar-thumb:hover { background: #F59E0B; }
/* Map Tweaks */
#map { width: 100%; height: 100px; border-radius: 1rem; border: 1px solid rgba(255,255,255,0.1); }
.leaflet-control-zoom { display: none; }
</style>
</head>
<body class="w-screen h-screen relative">
<!-- TOP: THE SHOWROOM (Visuals) -->
<div class="absolute top-0 left-0 right-0 h-[75vh] bg-black z-0 overflow-hidden group">
<!-- Navbar Overlay -->
<nav class="absolute top-6 left-8 right-8 z-50 flex justify-between items-center pointer-events-none">
<div class="flex items-center gap-3 cursor-pointer pointer-events-auto hover:scale-105 transition bg-black/40 backdrop-blur border border-white/10 px-4 py-2 rounded-full" onclick="window.location.href='explore.html'">
<i class="fa-solid fa-arrow-right text-white"></i>
<span class="text-sm font-bold text-white uppercase">العودة للبحث</span>
</div>
<div class="pointer-events-auto flex gap-3">
<span class="bg-dark-accent text-black font-black px-4 py-2 rounded-full shadow-[0_0_20px_rgba(245,158,11,0.5)]" id="propStatus">للبيع</span>
<span class="bg-black/60 backdrop-blur border border-white/20 text-white font-bold px-4 py-2 rounded-full" id="propType">عقار</span>
</div>
</nav>
<!-- Hero Image -->
<img id="fgImage" src="" class="absolute inset-0 w-full h-full object-cover transition duration-[3s] ease-out group-hover:scale-105 opacity-90">
<div id="imgOverlay" class="absolute inset-0 bg-gradient-to-t from-[#0B0F19] via-transparent to-black/30 pointer-events-none z-10"></div>
<!-- 3D Iframe (Hidden initially) -->
<iframe id="matterportFrame" class="hidden absolute inset-0 w-full h-full z-20 border-0" allowfullscreen allow="xr-spatial-tracking"></iframe>
<!-- 3D Toggle Button -->
<button id="start3DTourBtn" onclick="start3DTour()" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-30 group-hover:scale-110 transition duration-300">
<div class="w-32 h-32 bg-black/50 backdrop-blur-xl border border-white/30 rounded-full flex flex-col items-center justify-center text-white shadow-[0_0_50px_rgba(0,0,0,0.6)] cursor-pointer hover:bg-dark-accent hover:text-black hover:border-dark-accent transition-colors duration-300 group/btn">
<i class="fa-solid fa-vr-cardboard text-5xl mb-2 group-hover/btn:scale-110 transition"></i>
<span class="font-black text-xs uppercase tracking-widest">بدء الجولة</span>
</div>
</button>
</div>
<!-- BOTTOM: THE DASHBOARD DRAWER -->
<div class="showroom-drawer absolute bottom-0 left-0 right-0 h-[35vh] rounded-t-[3rem] z-40 p-8 pt-10 flex flex-row gap-8 overflow-x-auto drawer-scroll items-stretch">
<!-- SECTION 1: Property Info (Fixed Width) -->
<div class="w-[400px] shrink-0 flex flex-col justify-between border-l border-white/10 pl-8">
<div>
<h1 class="text-3xl font-black leading-tight text-white mb-2 line-clamp-2" id="propTitle">جاري التحميل...</h1>
<div class="text-sm text-gray-400 flex items-center gap-1 mb-4"><i class="fa-solid fa-location-dot text-dark-accent"></i> <span id="propLocation">...</span></div>
<div class="text-4xl font-black text-dark-accent tracking-wide" id="propPrice">... <span class="text-lg text-gray-400 font-normal">ج.م</span></div>
</div>
<!-- Specs Grid -->
<div class="grid grid-cols-4 gap-2 mt-4">
<div class="bg-white/5 border border-white/10 rounded-xl p-2 flex flex-col items-center justify-center text-center">
<i class="fa-solid fa-bed text-dark-accent text-lg mb-1"></i><span id="specBeds" class="font-bold text-sm">-</span>
</div>
<div class="bg-white/5 border border-white/10 rounded-xl p-2 flex flex-col items-center justify-center text-center">
<i class="fa-solid fa-bath text-dark-accent text-lg mb-1"></i><span id="specBaths" class="font-bold text-sm">-</span>
</div>
<div class="bg-white/5 border border-white/10 rounded-xl p-2 flex flex-col items-center justify-center text-center">
<i class="fa-solid fa-ruler-combined text-dark-accent text-lg mb-1"></i><span id="specArea" class="font-bold text-sm">-</span>
</div>
<div class="bg-white/5 border border-white/10 rounded-xl p-2 flex flex-col items-center justify-center text-center">
<i class="fa-solid fa-car text-dark-accent text-lg mb-1"></i><span id="specParking" class="font-bold text-sm">-</span>
</div>
</div>
</div>
<!-- SECTION 2: AI Analysis (Fixed Width) -->
<div class="w-[350px] shrink-0 flex flex-col gap-4 border-l border-white/10 pl-8">
<div class="flex items-center gap-2 mb-2">
<div class="w-8 h-8 rounded-full bg-dark-accent flex items-center justify-center text-black shadow-[0_0_10px_rgba(245,158,11,0.5)]"><i class="fa-solid fa-robot"></i></div>
<span class="text-xl font-black ai-gradient-text">التحليل الذكي</span>
</div>
<!-- AI Valuation Mini -->
<div class="bg-dark-surface/50 rounded-xl p-4 border border-white/5">
<div class="text-[10px] font-bold uppercase text-gray-400 mb-1"><i class="fa-solid fa-scale-balanced text-dark-accent"></i> تقييم السعر</div>
<div id="valTitleContainer" class="text-lg font-black text-white"><i class="fa-solid fa-spinner fa-spin"></i> جاري...</div>
</div>
<!-- Investment Scores -->
<div class="bg-dark-surface/50 rounded-xl p-4 border border-white/5 flex-1 flex flex-col justify-center space-y-4">
<div>
<div class="flex justify-between text-xs font-bold mb-1"><span class="text-gray-300">عائد الاستثمار (ROI)</span><span class="text-white" id="roiText">...</span></div>
<div class="w-full bg-black/50 rounded-full h-1.5"><div class="bg-blue-400 h-full rounded-full transition-all duration-1000 w-0" id="roiBar"></div></div>
</div>
<div>
<div class="flex justify-between text-xs font-bold mb-1"><span class="text-gray-300">النمو الرأسمالي</span><span class="text-white" id="growthText">...</span></div>
<div class="w-full bg-black/50 rounded-full h-1.5"><div class="bg-green-400 h-full rounded-full transition-all duration-1000 w-0" id="growthBar"></div></div>
</div>
</div>
</div>
<!-- SECTION 3: Communication & Map (Flexible Width) -->
<div class="flex-1 min-w-[400px] flex gap-6">
<!-- AI Chat -->
<div class="flex-1 flex flex-col bg-white/5 border border-white/10 rounded-2xl overflow-hidden h-full">
<div class="bg-white/5 p-3 border-b border-white/5 flex items-center gap-2">
<i class="fa-regular fa-comment-dots text-dark-accent"></i> <span class="text-sm font-bold">اسأل الذكاء الاصطناعي</span>
</div>
<div class="flex-1 overflow-y-auto drawer-scroll p-4 space-y-3" id="chatArea">
<div class="flex gap-2">
<div class="bg-white/10 p-2 rounded-xl rounded-tr-none text-xs text-gray-200 border border-white/5 shadow-sm max-w-[90%]">
أنا هنا للإجابة على أي أسئلة حول العقار.
</div>
</div>
</div>
<div class="p-2 border-t border-white/5 bg-black/20">
<div class="flex bg-black/50 rounded-lg overflow-hidden border border-white/10 focus-within:border-dark-accent transition">
<input type="text" id="chatInput" placeholder="اكتب سؤالك..." class="w-full bg-transparent text-xs text-white px-3 py-2 outline-none">
<button onclick="sendChatMessage()" class="text-dark-accent px-3 hover:text-white transition"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
<!-- Mini Map & Desc -->
<div class="w-[250px] shrink-0 flex flex-col gap-4 h-full">
<div id="map" class="shadow-inner flex-1 max-h-[120px]"></div>
<div class="flex-1 overflow-y-auto drawer-scroll text-xs text-gray-400 leading-relaxed pr-2" id="propDesc">
...
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const params = new URLSearchParams(window.location.search);
const propIndex = params.get('index') || 0;
let propData = null;
const storedAiData = sessionStorage.getItem('aiRecommendations');
if(storedAiData) {
const db = JSON.parse(storedAiData);
propData = db[propIndex];
}
// Fallback: If no data in session storage (e.g. direct link), fetch from backend
if(!propData) {
try {
const res = await fetch('/api/properties');
const json = await res.json();
if(json.properties && json.properties.length > 0) {
const db = json.properties;
propData = db[propIndex] || db[0]; // fallback to first if index invalid
}
} catch (e) {
console.error("Failed to fetch fallback properties", e);
}
}
if(propData) {
document.getElementById('propTitle').innerText = propData.title;
document.getElementById('propLocation').innerText = propData.location;
document.getElementById('propPrice').innerText = propData.price;
document.getElementById('propDesc').innerText = propData.description;
document.getElementById('propType').innerText = propData.type;
document.getElementById('propStatus').innerText = propData.status;
if(propData.image) document.getElementById('fgImage').src = propData.image;
if(propData.lat && propData.lng) {
const map = L.map('map', { zoomControl: false, attributionControl: false }).setView([propData.lat, propData.lng], 14);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png').addTo(map);
const icon = L.divIcon({ className: 'custom-div-icon', html: "<div style='width:12px;height:12px;background:#F59E0B;border-radius:50%;border:2px solid #000;'></div>", iconSize: [12, 12], iconAnchor: [6, 6] });
L.marker([propData.lat, propData.lng], {icon: icon}).addTo(map);
}
fetchInsights(propData);
}
document.getElementById('chatInput').addEventListener('keypress', function (e) {
if (e.key === 'Enter') sendChatMessage();
});
});
function start3DTour() {
const iframe = document.getElementById('matterportFrame');
const img = document.getElementById('fgImage');
const overlay = document.getElementById('imgOverlay');
const btn = document.getElementById('start3DTourBtn');
const storedAiData = sessionStorage.getItem('aiRecommendations');
const params = new URLSearchParams(window.location.search);
const propIndex = params.get('index') || 0;
let matterportId = "SxQL3iGyoDo";
if(storedAiData) {
const db = JSON.parse(storedAiData);
if(db[propIndex] && db[propIndex].matterport_id) matterportId = db[propIndex].matterport_id;
}
iframe.src = `https://my.matterport.com/show/?m=${matterportId}&play=1`;
iframe.classList.remove('hidden');
img.style.opacity = '0';
overlay.style.opacity = '0';
btn.classList.add('hidden');
}
async function fetchInsights(prop) {
try {
const response = await fetch('/api/property/insight', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ title: prop.title, price: prop.price, location: prop.location, type: prop.type, description: prop.description })
});
const result = await response.json();
if(result.success) {
const data = result.data;
document.getElementById('valTitleContainer').innerHTML = `<span class="text-green-400">${data.valuation_title}</span>`;
document.getElementById('roiText').innerText = data.roi_percentage;
document.getElementById('roiBar').style.width = data.roi_progress + '%';
document.getElementById('growthText').innerText = data.growth_percentage;
document.getElementById('growthBar').style.width = data.growth_progress + '%';
if(data.specs) {
document.getElementById('specBeds').innerText = data.specs.beds || '-';
document.getElementById('specBaths').innerText = data.specs.baths || '-';
document.getElementById('specArea').innerText = data.specs.area ? data.specs.area + 'm' : '-';
document.getElementById('specParking').innerText = data.specs.parking || '-';
}
}
} catch (error) {
document.getElementById('valTitleContainer').innerHTML = `<span class="text-red-400">فشل التحليل</span>`;
}
}
let chatContext = [];
async function sendChatMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if(!message) return;
const chatArea = document.getElementById('chatArea');
chatArea.insertAdjacentHTML('beforeend', `
<div class="flex gap-2 flex-row-reverse">
<div class="bg-dark-accent p-2 rounded-xl rounded-tl-none text-xs text-black border border-transparent max-w-[85%] font-bold shadow-sm">
${message}
</div>
</div>
`);
input.value = '';
chatArea.scrollTop = chatArea.scrollHeight;
const typingId = 'typing-' + Date.now();
chatArea.insertAdjacentHTML('beforeend', `<div id="${typingId}" class="flex gap-2">
<div class="bg-white/10 p-2 rounded-xl rounded-tr-none text-xs text-gray-400 shadow-sm border border-white/5"><i class="fa-solid fa-ellipsis fa-fade"></i></div>
</div>`);
chatArea.scrollTop = chatArea.scrollHeight;
if (chatContext.length === 0) {
const title = document.getElementById('propTitle').innerText;
const price = document.getElementById('propPrice').innerText;
const loc = document.getElementById('propLocation').innerText;
const desc = document.getElementById('propDesc').innerText;
chatContext.push({ "role": "system", "content": `أنت مساعد عقاري ذكي. المستخدم يسأل عن العقار التالي:
العنوان: ${title}
السعر: ${price}
المنطقة: ${loc}
الوصف: ${desc}
أجب باختصار وبشكل مباشر ومفيد بناءً على هذه المعلومات فقط.` });
}
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ message: message, history: chatContext })
});
const data = await response.json();
document.getElementById(typingId).remove();
const reply = data.reply || "عذراً، حدث خطأ.";
chatArea.insertAdjacentHTML('beforeend', `
<div class="flex gap-2">
<div class="bg-white/10 p-2 rounded-xl rounded-tr-none text-xs text-white border border-white/5 max-w-[85%] shadow-sm leading-relaxed">
${reply.replace(/\n/g, '<br>')}
</div>
</div>
`);
chatArea.scrollTop = chatArea.scrollHeight;
chatContext.push({"role": "user", "content": message}, {"role": "assistant", "content": reply});
} catch (e) {
document.getElementById(typingId).remove();
chatArea.insertAdjacentHTML('beforeend', `<div class="text-red-400 text-[10px]">فشل الاتصال.</div>`);
}
}
</script>
</body>
</html>