File size: 19,779 Bytes
a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 3f5ff7f 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 3f5ff7f 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 771f178 3f5ff7f 771f178 a0a0034 771f178 a0a0034 771f178 a0a0034 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | <!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>
|