import { Suspense, useMemo, useRef, useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; import { ArrowRight, Bath, Bed, CheckCircle2, Clock, Compass, Folder, Layers, MapPin, MapPinned, Maximize2, Search, Tag, XCircle, } from 'lucide-react'; import { Canvas } from '@react-three/fiber'; import { Bounds, Html, OrbitControls, useGLTF } from '@react-three/drei'; import * as THREE from 'three'; import { useStore } from '@/store/useStore'; import { useCurrency } from '@/store/useCurrencyStore'; import type { Unit } from '@/types'; // Penthouse preview images — one per unit (u1–u8) for card thumbnails const UNIT_PREVIEWS: Record = { u1: '/penthouse-images/1.jpg', u2: '/penthouse-images/2.jpg', u3: '/penthouse-images/3.jpg', u4: '/penthouse-images/4.jpg', u5: '/penthouse-images/5.jpg', u6: '/penthouse-images/6.jpg', u7: '/penthouse-images/7.jpg', u8: '/penthouse-images/8.jpg', }; // Blueprint floor plan images for the Blueprint Studio viewer const HOUSE_1_BLUEPRINT = new URL( '../../../assets/House Floor Plans/House 1/f6b441fc43a460a957df992433ee39ca.jpg', import.meta.url ).href; const HOUSE_2_BLUEPRINT = new URL( '../../../assets/House Floor Plans/House 2/ee2057aab582951894fea5b1f56ea27e.jpg', import.meta.url ).href; const HOUSE_1_GLB = '/models/house1/house1.glb'; const HOUSE_2_GLB = '/models/house2/house2.glb'; const MAP_EMBED_URL = 'https://www.google.com/maps?q=Dubai+Marina&output=embed'; // Preload GLB files for faster loading useGLTF.preload(HOUSE_1_GLB); useGLTF.preload(HOUSE_2_GLB); function StatusBadge({ status }: { status: Unit['status'] }) { const config = { available: { icon: CheckCircle2, label: 'Available', color: 'text-green-300 bg-green-500/20 border-green-500/30' }, reserved: { icon: Clock, label: 'Reserved', color: 'text-amber-300 bg-amber-500/20 border-amber-500/30' }, sold: { icon: XCircle, label: 'Sold', color: 'text-zinc-300 bg-zinc-500/20 border-zinc-500/30' }, hold: { icon: Clock, label: 'On Hold', color: 'text-red-300 bg-red-500/20 border-red-500/30' }, } as const; const { icon: Icon, label, color } = config[status]; return ( {label} ); } function MacClose({ onClose }: { onClose: () => void }) { return (
); } function GlbModel({ url }: { url: string }) { const { scene } = useGLTF(url); const model = useMemo(() => { const cloned = scene.clone(); // Auto-fit: normalize to a standard size regardless of original scale const box = new THREE.Box3().setFromObject(cloned); const size = new THREE.Vector3(); box.getSize(size); const maxDim = Math.max(size.x, size.y, size.z) || 1; const scale = 5 / maxDim; cloned.scale.setScalar(scale); // Re-center after scaling const box2 = new THREE.Box3().setFromObject(cloned); const center = new THREE.Vector3(); box2.getCenter(center); cloned.position.sub(center); return cloned; }, [scene]); return ; } function Viewer3D({ unit }: { unit: Unit }) { const glbUrl = unit.id === 'u1' || unit.id === 'u2' ? HOUSE_2_GLB : HOUSE_1_GLB; return (
Loading 3D model…
} > ); } // ─── Per-unit enriched data ──────────────────────────────────────────────── const UNIT_DETAILS: Record = { u1: { bedrooms: 4, bathrooms: 4, parking: 2, description: 'Sky-high penthouse with unobstructed panoramic sea views, private terrace, and bespoke interiors across two levels.', features: ['Private Rooftop Terrace', 'Smart Home Automation', 'Floor-to-Ceiling Glazing', 'Private Elevator Lobby', 'Chef\'s Kitchen', 'Maid\'s Room'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '40%' }, { label: 'On Handover', value: '50%' }], }, u2: { bedrooms: 3, bathrooms: 3, parking: 2, description: 'Expansive penthouse overlooking the sea and marina, featuring a wraparound terrace and premium finishes throughout.', features: ['Wraparound Terrace', 'Marina & Sea Views', 'Smart Home System', 'Private Pool', 'Walk-in Wardrobes', 'Maid\'s Room'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '40%' }, { label: 'On Handover', value: '50%' }], }, u3: { bedrooms: 3, bathrooms: 3, parking: 2, description: 'Generous 3-bedroom residence with sweeping sea views, open-plan living, and premium finishes on the 45th floor.', features: ['Sea View', 'Open-Plan Living', 'Balcony', 'Built-in Wardrobes', 'Laundry Room', 'Storage Room'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '50%' }, { label: 'On Handover', value: '40%' }], }, u4: { bedrooms: 3, bathrooms: 3, parking: 2, description: 'Elegant 3-bedroom apartment with marina views, contemporary design, and a spacious open-plan layout.', features: ['Marina View', 'Open-Plan Living', 'Balcony', 'Built-in Wardrobes', 'Laundry Room', 'Storage Room'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '50%' }, { label: 'On Handover', value: '40%' }], }, u5: { bedrooms: 2, bathrooms: 2, parking: 1, description: 'Bright 2-bedroom apartment with sea views and a modern open-plan kitchen and living area on the 44th floor.', features: ['Sea View', 'Open-Plan Kitchen', 'Balcony', 'Built-in Wardrobes', 'Laundry Closet'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '50%' }, { label: 'On Handover', value: '40%' }], }, u6: { bedrooms: 2, bathrooms: 2, parking: 1, description: 'Contemporary 2-bedroom apartment with city views, modern interiors, and a private balcony on the 44th floor.', features: ['City View', 'Open-Plan Kitchen', 'Balcony', 'Built-in Wardrobes', 'Laundry Closet'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '50%' }, { label: 'On Handover', value: '40%' }], }, u7: { bedrooms: 1, bathrooms: 1, parking: 1, description: 'Stylish 1-bedroom apartment with sea views and a well-appointed open-plan layout on the 43rd floor.', features: ['Sea View', 'Open-Plan Layout', 'Balcony', 'Built-in Wardrobe'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '60%' }, { label: 'On Handover', value: '30%' }], }, u8: { bedrooms: 1, bathrooms: 1, parking: 1, description: 'Modern 1-bedroom apartment with city views and a compact, efficient layout on the 43rd floor.', features: ['City View', 'Open-Plan Layout', 'Balcony', 'Built-in Wardrobe'], paymentPlan: [{ label: 'On Booking', value: '10%' }, { label: 'During Construction', value: '60%' }, { label: 'On Handover', value: '30%' }], }, }; const DEFAULT_DETAILS = UNIT_DETAILS['u1']; // ─── Property Detail Modal ─────────────────────────────────────────────────── function PropertyDetailModal({ unit, onClose, onOpen3D, onOpenBlueprint, }: { unit: Unit; onClose: () => void; onOpen3D: (unit: Unit) => void; onOpenBlueprint: (unit: Unit) => void; }) { const details = UNIT_DETAILS[unit.id] ?? DEFAULT_DETAILS; const preview = UNIT_PREVIEWS[unit.id] ?? UNIT_PREVIEWS['u1']; const { formatAmount } = useCurrency(); const statusColors: Record = { available: 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10', reserved: 'text-amber-300 border-amber-400/30 bg-amber-500/10', sold: 'text-red-300 border-red-400/30 bg-red-500/10', hold: 'text-zinc-300 border-zinc-400/30 bg-zinc-500/10', }; const pricePerSqm = Math.round(unit.price / unit.area); return ( {/* Backdrop */}
{/* Modal */} {/* Header bar */}
Unit {unit.unitNumber} · Floor {unit.floor}
{unit.status}
{/* Body — scrollable */}
{/* Hero image */}
{unit.unitNumber}
{/* Overlay price */}

Starting from

{formatAmount(unit.price)}

{formatAmount(pricePerSqm)} / m²

{/* Content grid */}
{/* Left column */}
{/* Description */}

{unit.type === 'penthouse' ? 'Penthouse' : unit.type.toUpperCase()} · {unit.unitNumber}

{details.description}

{/* Key stats */}
{[ { icon: , label: 'Bedrooms', value: details.bedrooms }, { icon: , label: 'Bathrooms', value: details.bathrooms }, { icon: , label: 'Area', value: `${unit.area} m²` }, { icon: , label: 'Floor', value: unit.floor }, ].map((stat) => (
{stat.icon} {stat.value} {stat.label}
))}
{/* Additional info row */}
{[ { icon: , label: 'View', value: unit.view }, { icon: , label: 'Parking', value: `${details.parking} Bay${details.parking > 1 ? 's' : ''}` }, { icon: , label: 'Type', value: unit.type.toUpperCase() }, ].map((item) => (
{item.icon} {item.label}

{item.value}

))}
{/* Features */}

Features & Amenities

{details.features.map((f) => ( {f} ))}
{/* Quick-launch buttons */}
{/* Right column */}
{/* Pricing card */}

Pricing

{formatAmount(unit.price)}

{formatAmount(pricePerSqm)} per m²

Unit Area {unit.area} m²
Floor {unit.floor}
Parking {details.parking} Bay{details.parking > 1 ? 's' : ''}
{/* Payment plan */}

Payment Plan

{details.paymentPlan.map((step, i) => (
{i + 1}
{step.label} {step.value}
))}
{/* Status card */}

Availability

{unit.status === 'available' ? '✓ Available Now' : unit.status === 'reserved' ? '⏳ Reserved' : unit.status === 'sold' ? '✗ Sold' : '⏸ On Hold'}

Last updated: {unit.lastUpdated.toLocaleDateString('en-AE', { day: 'numeric', month: 'short', year: 'numeric' })}

); } function UnitCard({ unit, onOpen3D, onOpenBlueprint, onViewDetails, }: { unit: Unit; onOpen3D: (unit: Unit) => void; onOpenBlueprint: (unit: Unit) => void; onViewDetails: (unit: Unit) => void; }) { const preview = UNIT_PREVIEWS[unit.id] ?? UNIT_PREVIEWS['u1']; const [hovered, setHovered] = useState(false); const { formatAmount } = useCurrency(); // Status accent color for glow const statusGlow = unit.status === 'available' ? 'rgba(34,197,94,0.15)' : unit.status === 'reserved' ? 'rgba(245,158,11,0.15)' : unit.status === 'sold' ? 'rgba(139,92,246,0.12)' : 'rgba(239,68,68,0.12)'; return ( onViewDetails(unit)} whileHover={{ y: -3, scale: 1.008 }} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} transition={{ type: 'spring', stiffness: 400, damping: 28 }} > {/* Ambient status glow — top-right corner */} {/* Inner padding wrapper */}
{/* Header row */}
Unit Folder

{unit.unitNumber}

Floor {unit.floor}

{/* Image / 3D preview */}
{!hovered ? ( {`${unit.unitNumber} ) : (
)}
{/* Tags */}
{unit.type} {unit.area} m² {unit.view}
{/* Price */}

Starting from

{formatAmount(unit.price)}

{/* Divider */}
{/* Actions */}
); } function BlueprintViewer({ blueprintImage, unitNumber }: { blueprintImage: string; unitNumber: string }) { const containerRef = useRef(null); const imgRef = useRef(null); // scale: 1 = image natural size. We compute fitScale once image loads. const [scale, setScale] = useState(1); const [fitScale, setFitScale] = useState(1); const [offset, setOffset] = useState({ x: 0, y: 0 }); const [dragging, setDragging] = useState(false); const dragStart = useRef({ mx: 0, my: 0, ox: 0, oy: 0 }); // Compute fit-to-height scale when image loads const handleImageLoad = () => { const img = imgRef.current; const container = containerRef.current; if (!img || !container) return; const naturalW = img.naturalWidth; const naturalH = img.naturalHeight; const containerH = container.clientHeight; const containerW = container.clientWidth; // Fit to height, allow empty space on sides const byHeight = containerH / naturalH; // But don't exceed container width either const byWidth = containerW / naturalW; const fit = Math.min(byHeight, byWidth) * 0.92; // 92% so there's a small margin setFitScale(fit); setScale(fit); setOffset({ x: 0, y: 0 }); }; const clampScale = (s: number) => Math.max(fitScale * 0.5, Math.min(fitScale * 8, s)); const handleWheel = (e: React.WheelEvent) => { e.preventDefault(); const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12; setScale((prev) => clampScale(prev * factor)); }; const handleMouseDown = (e: React.MouseEvent) => { e.preventDefault(); setDragging(true); dragStart.current = { mx: e.clientX, my: e.clientY, ox: offset.x, oy: offset.y }; }; const handleMouseMove = (e: React.MouseEvent) => { if (!dragging) return; setOffset({ x: dragStart.current.ox + (e.clientX - dragStart.current.mx), y: dragStart.current.oy + (e.clientY - dragStart.current.my), }); }; const handleMouseUp = () => setDragging(false); const zoomIn = () => setScale((prev) => clampScale(prev * 1.25)); const zoomOut = () => setScale((prev) => clampScale(prev / 1.25)); const resetView = () => { setScale(fitScale); setOffset({ x: 0, y: 0 }); }; const zoomPercent = Math.round((scale / fitScale) * 100); return (
{/* Blueprint image — centered, transformed */}
{`${unitNumber}
{/* Zoom controls */}
{/* Zoom level indicator */}
{zoomPercent}%
); } function StudioWindow({ unit, mode, onClose, }: { unit: Unit | null; mode: '3d' | 'blueprint' | null; onClose: () => void; }) { if (!unit || !mode) return null; const blueprintImage = unit.id === 'u1' || unit.id === 'u2' ? HOUSE_2_BLUEPRINT : HOUSE_1_BLUEPRINT; return (

{mode === '3d' ? '3D Unit Studio' : 'Blueprint Studio'} - {unit.unitNumber}

{mode === '3d' ? : }
{mode === '3d' && (

Model Viewport

Interactive 3D model loaded from OBJ + MTL + textures.

Controls

Mouse: rotate, pan, zoom

Model source: House {unit.id === 'u1' || unit.id === 'u2' ? '2' : '1'}

)}
); } function RightMapPane({ units }: { units: Unit[] }) { const { formatAmount } = useCurrency(); return (