{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "virtual-tour-panorama",
  "title": "Virtual Tour Panorama",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/VirtualTourPanorama.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  ArrowRight,\n  Bath,\n  Bed,\n  Check,\n  ChevronDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronUp,\n  Compass,\n  Glasses,\n  Home,\n  Info,\n  Map as MapIcon,\n  Maximize2,\n  Minimize2,\n  Navigation,\n  Pause,\n  Play,\n  RotateCcw,\n  Ruler,\n  Share2,\n  Sofa,\n  Sun,\n  Utensils,\n  Volume2,\n  VolumeX,\n  ZoomIn,\n  ZoomOut,\n} from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\nimport { TourFloorPlanNavigator } from './TourFloorPlanNavigator'\nimport { TourHotspotModal } from './TourHotspotModal'\nimport { TourPanoramaScene } from './TourPanoramaScene'\nimport { TourRoomDetails } from './TourRoomDetails'\nimport { defaultRooms } from './virtual-tour-panorama-data'\nimport { type RoomData, type RoomHotspot } from './virtual-tour-panorama-types'\n\nexport interface VirtualTourPanoramaProps {\n  rooms?: RoomData[]\n}\n\nfunction getRoomIcon(roomId: string) {\n  switch (roomId) {\n    case 'master-bedroom':\n      return Bed\n    case 'living-room':\n      return Sofa\n    case 'gourmet-kitchen':\n      return Utensils\n    case 'rooftop-terrace':\n      return Sun\n    case 'spa-bath':\n      return Bath\n    default:\n      return Home\n  }\n}\n\nexport function VirtualTourPanorama({ rooms }: VirtualTourPanoramaProps) {\n  const allRooms = React.useMemo(() => (rooms && rooms.length > 0 ? rooms : defaultRooms), [rooms])\n\n  // State variables\n  const [currentRoomId, setCurrentRoomId] = React.useState<string>('master-bedroom')\n  const [yaw, setYaw] = React.useState<number>(0)\n  const [pitch, setPitch] = React.useState<number>(0)\n  const [zoom, setZoom] = React.useState<number>(1.0)\n\n  const [isDragging, setIsDragging] = React.useState<boolean>(false)\n  const dragStartRef = React.useRef({ x: 0, y: 0, yaw: 0, pitch: 0 })\n\n  const [isAutoRotating, setIsAutoRotating] = React.useState<boolean>(false)\n  const [isMinimapOpen, setIsMinimapOpen] = React.useState<boolean>(true)\n  const [isMeasurementMode, setIsMeasurementMode] = React.useState<boolean>(false)\n  const [measurementUnit, setMeasurementUnit] = React.useState<'imperial' | 'metric'>('imperial')\n  const [isVrMode, setIsVrMode] = React.useState<boolean>(false)\n  const [isAudioActive, setIsAudioActive] = React.useState<boolean>(false)\n  const [isFullscreen, setIsFullscreen] = React.useState<boolean>(false)\n  const [copiedToast, setCopiedToast] = React.useState<boolean>(false)\n  const [isRoomTransitioning, setIsRoomTransitioning] = React.useState<boolean>(false)\n  const [activeInfoHotspot, setActiveInfoHotspot] = React.useState<RoomHotspot | null>(null)\n  const [hoveredHotspotId, setHoveredHotspotId] = React.useState<string | null>(null)\n\n  // Current Room\n  const currentRoom = React.useMemo(() => {\n    return allRooms.find((r) => r.id === currentRoomId) || allRooms[0]\n  }, [allRooms, currentRoomId])\n\n  // Normalized Compass Heading\n  const compassHeading = React.useMemo(() => {\n    const normalized = ((yaw % 360) + 360) % 360\n    return Math.round(normalized)\n  }, [yaw])\n\n  const compassDirection = React.useMemo(() => {\n    const h = compassHeading\n    if (h >= 337.5 || h < 22.5) return 'N'\n    if (h >= 22.5 && h < 67.5) return 'NE'\n    if (h >= 67.5 && h < 112.5) return 'E'\n    if (h >= 112.5 && h < 157.5) return 'SE'\n    if (h >= 157.5 && h < 202.5) return 'S'\n    if (h >= 202.5 && h < 247.5) return 'SW'\n    if (h >= 247.5 && h < 292.5) return 'W'\n    return 'NW'\n  }, [compassHeading])\n\n  // Auto-rotation loop\n  React.useEffect(() => {\n    let autoRotateRaf: number | null = null\n\n    const updateAutoRotate = () => {\n      if (isAutoRotating && !isDragging) {\n        setYaw((prev) => (prev + 0.18) % 360)\n      }\n      autoRotateRaf = requestAnimationFrame(updateAutoRotate)\n    }\n\n    autoRotateRaf = requestAnimationFrame(updateAutoRotate)\n    return () => {\n      if (autoRotateRaf) cancelAnimationFrame(autoRotateRaf)\n    }\n  }, [isAutoRotating, isDragging])\n\n  // Pan step helpers\n  const panStep = (dx: number, dy: number) => {\n    setYaw((prev) => (prev + dx + 3600) % 360)\n    setPitch((prev) => Math.max(-22, Math.min(22, prev + dy)))\n  }\n\n  const zoomStep = (delta: number) => {\n    setZoom((prev) => Math.max(0.85, Math.min(1.5, prev + delta)))\n  }\n\n  const resetView = () => {\n    setYaw(0)\n    setPitch(0)\n    setZoom(1.0)\n  }\n\n  // Keyboard navigation\n  React.useEffect(() => {\n    const handleKeydown = (e: KeyboardEvent) => {\n      if (['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement)?.tagName)) return\n      if (e.key === 'ArrowLeft') panStep(-10, 0)\n      if (e.key === 'ArrowRight') panStep(10, 0)\n      if (e.key === 'ArrowUp') panStep(0, 5)\n      if (e.key === 'ArrowDown') panStep(0, -5)\n      if (e.key === '+' || e.key === '=') zoomStep(0.1)\n      if (e.key === '-' || e.key === '_') zoomStep(-0.1)\n      if (e.key === 'm' || e.key === 'M') setIsMinimapOpen((prev) => !prev)\n      if (e.key === 'r' || e.key === 'R') setIsMeasurementMode((prev) => !prev)\n    }\n\n    window.addEventListener('keydown', handleKeydown)\n    return () => window.removeEventListener('keydown', handleKeydown)\n  }, [])\n\n  // Room transition\n  const switchRoom = (roomId: string) => {\n    if (roomId === currentRoomId) return\n    setIsRoomTransitioning(true)\n    setActiveInfoHotspot(null)\n\n    setTimeout(() => {\n      setCurrentRoomId(roomId)\n      setYaw(0)\n      setPitch(0)\n      setTimeout(() => {\n        setIsRoomTransitioning(false)\n      }, 280)\n    }, 220)\n  }\n\n  // Mouse / Touch Interaction Handlers\n  const onPointerDown = (e: React.MouseEvent | React.TouchEvent) => {\n    setIsDragging(true)\n    const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX\n    const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n    dragStartRef.current = {\n      x: clientX,\n      y: clientY,\n      yaw,\n      pitch,\n    }\n  }\n\n  const onPointerMove = (e: React.MouseEvent | React.TouchEvent) => {\n    if (!isDragging) return\n    const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX\n    const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY\n\n    const deltaX = clientX - dragStartRef.current.x\n    const deltaY = clientY - dragStartRef.current.y\n\n    setYaw((dragStartRef.current.yaw - deltaX * 0.25 + 3600) % 360)\n    setPitch(Math.max(-22, Math.min(22, dragStartRef.current.pitch + deltaY * 0.18)))\n  }\n\n  const onPointerUp = () => {\n    setIsDragging(false)\n  }\n\n  const copyTourShareLink = () => {\n    setCopiedToast(true)\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      navigator.clipboard.writeText('https://uipkge.dev/tour/742-evergreen-terrace')\n    }\n    setTimeout(() => {\n      setCopiedToast(false)\n    }, 3200)\n  }\n\n  const toggleFullscreen = () => {\n    setIsFullscreen((prev) => !prev)\n  }\n\n  const getHotspotStyle = (hotspot: RoomHotspot) => {\n    const relativeX = Math.max(8, Math.min(92, (hotspot.x - (yaw / 360) * 100 + 150) % 100))\n    const relativeY = Math.max(8, Math.min(88, hotspot.y + pitch * 0.4))\n    return {\n      left: `${relativeX}%`,\n      top: `${relativeY}%`,\n    }\n  }\n\n  const CurrentRoomIcon = getRoomIcon(currentRoom.id)\n\n  return (\n    <div\n      data-slot=\"virtual-tour-panorama\"\n      className={`bg-background text-foreground w-full space-y-4 font-sans transition-[background-color,padding] duration-300 ${\n        isFullscreen ? 'fixed inset-0 z-50 overflow-y-auto bg-black p-4 sm:p-6' : ''\n      }`}\n    >\n      {/* Top Header Bar */}\n      <header className=\"bg-card rounded-xl border p-3.5 shadow-xs sm:px-5 sm:py-3\">\n        <div className=\"flex flex-wrap items-center justify-between gap-3\">\n          {/* Property Identity & Room Badge */}\n          <div className=\"flex flex-wrap items-center gap-2.5 sm:gap-3\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"bg-primary/10 text-primary border-primary/30 flex size-8 items-center justify-center rounded-lg border shadow-xs\">\n                <Compass className=\"size-4 animate-[spin_12s_linear_infinite]\" />\n              </div>\n              <div>\n                <div className=\"flex items-center gap-2\">\n                  <h2 className=\"text-foreground text-sm font-bold tracking-tight sm:text-base\">\n                    742 Evergreen Terrace\n                  </h2>\n                  <span className=\"text-muted-foreground hidden text-xs font-medium sm:inline\">360° Virtual Tour</span>\n                </div>\n                <p className=\"text-muted-foreground text-xs\">Penthouse Residence 14B · The Evergreen Collection</p>\n              </div>\n            </div>\n\n            <Separator orientation=\"vertical\" className=\"hidden h-6 sm:block\" />\n\n            {/* Dynamic Active Room Badge */}\n            <Badge\n              variant=\"outline\"\n              className=\"border-primary/40 bg-primary/10 text-primary gap-1.5 px-2.5 py-1 text-xs font-semibold\"\n            >\n              <CurrentRoomIcon className=\"size-3.5\" />\n              <span>{currentRoom.badgeName}</span>\n            </Badge>\n\n            {/* Resolution & Live Feed Indicator */}\n            <div className=\"text-muted-foreground hidden items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium md:flex\">\n              <span className=\"bg-success size-1.5 animate-pulse rounded-full\" />\n              <span>8K Ultra-HD Pano</span>\n              <span>·</span>\n              <span>{currentRoom.floor}</span>\n            </div>\n          </div>\n\n          {/* Top Right Quick Actions */}\n          <div className=\"flex items-center gap-2\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className={`h-8 gap-1.5 px-2.5 text-xs ${isAudioActive ? 'border-info/40 bg-info/10 text-info' : ''}`}\n              aria-label={isAudioActive ? 'Mute ambient soundscape' : 'Play ambient soundscape'}\n              onClick={() => setIsAudioActive((prev) => !prev)}\n            >\n              {isAudioActive ? (\n                <Volume2 className=\"text-info size-3.5\" />\n              ) : (\n                <VolumeX className=\"text-muted-foreground size-3.5\" />\n              )}\n              <span className=\"hidden md:inline\">{isAudioActive ? 'Sound On' : 'Ambience'}</span>\n            </Button>\n\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className={`h-8 gap-1.5 px-2.5 text-xs ${isVrMode ? 'border-chart-1/40 bg-chart-1/10 text-chart-1' : ''}`}\n              aria-label={isVrMode ? 'Exit VR mode' : 'Enter VR mode'}\n              onClick={() => setIsVrMode((prev) => !prev)}\n            >\n              <Glasses className=\"size-3.5\" />\n              <span className=\"hidden sm:inline\">{isVrMode ? 'VR Active' : 'VR Mode'}</span>\n            </Button>\n\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"relative h-8 gap-1.5 px-2.5 text-xs\"\n              aria-label=\"Share 360 Tour\"\n              onClick={copyTourShareLink}\n            >\n              {copiedToast ? <Check className=\"text-success size-3.5\" /> : <Share2 className=\"size-3.5\" />}\n              <span className=\"hidden sm:inline\">{copiedToast ? 'Copied!' : 'Share'}</span>\n            </Button>\n\n            <Button\n              variant=\"outline\"\n              size=\"icon\"\n              className=\"size-8\"\n              aria-label={isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}\n              onClick={toggleFullscreen}\n            >\n              {isFullscreen ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n            </Button>\n          </div>\n        </div>\n      </header>\n\n      {/* 360 Panorama Viewport Container */}\n      <div\n        className=\"relative aspect-video min-h-[480px] w-full overflow-hidden rounded-xl border border-zinc-800 bg-zinc-950 text-white shadow-sm select-none sm:min-h-[580px]\"\n        onMouseDown={onPointerDown}\n        onMouseMove={onPointerMove}\n        onMouseUp={onPointerUp}\n        onMouseLeave={onPointerUp}\n        onTouchStart={onPointerDown}\n        onTouchMove={onPointerMove}\n        onTouchEnd={onPointerUp}\n      >\n        <TourPanoramaScene\n          currentRoom={currentRoom}\n          yaw={yaw}\n          pitch={pitch}\n          zoom={zoom}\n          isRoomTransitioning={isRoomTransitioning}\n          isMeasurementMode={isMeasurementMode}\n          measurementUnit={measurementUnit}\n        />\n\n        {/* Interactive Hotspot Pins Layer */}\n        <div className=\"pointer-events-auto absolute inset-0 size-full\">\n          {currentRoom.hotspots.map((hotspot) => (\n            <div\n              key={hotspot.id}\n              className=\"absolute -translate-x-1/2 -translate-y-1/2 transition-transform duration-100\"\n              style={getHotspotStyle(hotspot)}\n              onMouseEnter={() => setHoveredHotspotId(hotspot.id)}\n              onMouseLeave={() => setHoveredHotspotId(null)}\n            >\n              {/* PORTAL HOTSPOT: Room Navigation */}\n              {hotspot.type === 'portal' && (\n                <div className=\"group relative flex flex-col items-center\">\n                  <span className=\"bg-success absolute -inset-2.5 rounded-full opacity-60 duration-1000\" />\n                  <span className=\"bg-success/40 absolute -inset-1 rounded-full opacity-80 blur-xs\" />\n\n                  <button\n                    type=\"button\"\n                    className=\"border-success bg-success/90 focus-visible:ring-success relative flex size-9 items-center justify-center rounded-full border-2 text-white shadow-xl backdrop-blur-md transition-transform hover:scale-115 focus-visible:ring-2 focus-visible:outline-none\"\n                    aria-label={hotspot.title}\n                    onClick={(e) => {\n                      e.stopPropagation()\n                      if (hotspot.targetRoomId) switchRoom(hotspot.targetRoomId)\n                    }}\n                  >\n                    <Navigation className=\"size-4\" />\n                  </button>\n\n                  <div\n                    className={`border-success/40 pointer-events-none absolute top-11 z-30 flex w-52 flex-col items-center rounded-lg border bg-zinc-950/95 p-2 text-center shadow-sm backdrop-blur-md transition-colors duration-200 ${\n                      hoveredHotspotId === hotspot.id ? 'translate-y-0 opacity-100' : 'translate-y-1 opacity-0'\n                    }`}\n                  >\n                    <div className=\"text-success flex items-center gap-1 text-xs font-bold\">\n                      <span>{hotspot.title}</span>\n                    </div>\n                    <p className=\"text-muted-foreground mt-0.5 text-xs\">{hotspot.subtitle}</p>\n                    <span className=\"text-success mt-1 inline-flex items-center gap-1 font-mono text-xs font-semibold\">\n                      Click to Transition <ArrowRight className=\"size-3\" />\n                    </span>\n                  </div>\n                </div>\n              )}\n\n              {/* INFO HOTSPOT: Feature Inspection */}\n              {hotspot.type === 'info' && (\n                <div className=\"group relative flex flex-col items-center\">\n                  <span className=\"bg-info absolute -inset-2.5 rounded-full opacity-60 duration-1000\" />\n                  <span className=\"bg-info/40 absolute -inset-1 rounded-full opacity-80 blur-xs\" />\n\n                  <button\n                    type=\"button\"\n                    className=\"border-info bg-info/90 relative flex size-9 items-center justify-center rounded-full border-2 text-white shadow-xl backdrop-blur-md transition-transform hover:scale-115 focus-visible:ring-2 focus-visible:ring-sky-400 focus-visible:outline-none\"\n                    aria-label={hotspot.title}\n                    onClick={(e) => {\n                      e.stopPropagation()\n                      setActiveInfoHotspot(hotspot)\n                    }}\n                  >\n                    <Info className=\"size-4\" />\n                  </button>\n\n                  <div\n                    className={`border-info/40 pointer-events-none absolute top-11 z-30 flex w-52 flex-col items-center rounded-lg border bg-zinc-950/95 p-2 text-center shadow-sm backdrop-blur-md transition-colors duration-200 ${\n                      hoveredHotspotId === hotspot.id ? 'translate-y-0 opacity-100' : 'translate-y-1 opacity-0'\n                    }`}\n                  >\n                    <div className=\"text-info flex items-center gap-1 text-xs font-bold\">\n                      <Info className=\"size-3\" />\n                      <span>{hotspot.title}</span>\n                    </div>\n                    <p className=\"text-muted-foreground mt-0.5 text-xs\">{hotspot.subtitle}</p>\n                    <span className=\"text-info mt-1 inline-flex items-center gap-1 font-mono text-xs font-semibold\">\n                      Click to Inspect Specs\n                    </span>\n                  </div>\n                </div>\n              )}\n            </div>\n          ))}\n        </div>\n\n        {/* Top Overlay HUD: Compass, Pitch, FOV & Controls */}\n        <div className=\"pointer-events-none absolute inset-x-3.5 top-3.5 z-20 flex flex-wrap items-start justify-between gap-2\">\n          <div className=\"pointer-events-auto flex items-center gap-2.5 rounded-lg border border-zinc-700/60 bg-zinc-900/85 px-3 py-1.5 shadow-lg backdrop-blur-md\">\n            <div className=\"flex items-center gap-1.5 font-mono text-xs font-bold text-zinc-100\">\n              <Compass\n                className=\"text-primary size-3.5 transition-transform duration-100\"\n                style={{ transform: `rotate(${compassHeading}deg)` }}\n              />\n              <span>\n                {compassHeading}° {compassDirection}\n              </span>\n            </div>\n            <Separator orientation=\"vertical\" className=\"h-3.5 bg-zinc-700\" />\n            <span className=\"text-muted-foreground font-mono text-xs\">FOV 85°</span>\n            <Separator orientation=\"vertical\" className=\"h-3.5 bg-zinc-700\" />\n            <span className=\"text-muted-foreground font-mono text-xs\">\n              Pitch {pitch > 0 ? `+${Math.round(pitch)}°` : `${Math.round(pitch)}°`}\n            </span>\n          </div>\n\n          <div className=\"pointer-events-auto flex items-center gap-2\">\n            {isVrMode && (\n              <div className=\"border-chart-1/50 bg-card/90 text-chart-1 flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-xs font-semibold backdrop-blur-md\">\n                <Glasses className=\"size-3.5 animate-pulse\" />\n                <span>Gyroscope / VR Sensor Active</span>\n              </div>\n            )}\n\n            {isMeasurementMode && (\n              <div className=\"border-destructive/50 flex items-center gap-1 rounded-lg border bg-zinc-900/90 p-1 backdrop-blur-md\">\n                <Button\n                  size=\"sm\"\n                  variant={measurementUnit === 'imperial' ? 'destructive' : 'ghost'}\n                  className=\"h-6 px-2 text-xs\"\n                  onClick={() => setMeasurementUnit('imperial')}\n                >\n                  Imperial (ft)\n                </Button>\n                <Button\n                  size=\"sm\"\n                  variant={measurementUnit === 'metric' ? 'destructive' : 'ghost'}\n                  className=\"h-6 px-2 text-xs\"\n                  onClick={() => setMeasurementUnit('metric')}\n                >\n                  Metric (m)\n                </Button>\n              </div>\n            )}\n          </div>\n        </div>\n\n        {/* Floating 360 Pan & Zoom Controls */}\n        <div className=\"pointer-events-auto absolute top-16 right-3.5 z-20 hidden flex-col items-center gap-1 rounded-lg border border-zinc-800 bg-zinc-900/85 p-1 shadow-xl backdrop-blur-md sm:flex\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Pan Up\"\n            onClick={() => panStep(0, 8)}\n          >\n            <ChevronUp className=\"size-4\" />\n          </Button>\n          <div className=\"flex items-center gap-1\">\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n              aria-label=\"Pan Left\"\n              onClick={() => panStep(-15, 0)}\n            >\n              <ChevronLeft className=\"size-4\" />\n            </Button>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n              aria-label=\"Reset View\"\n              onClick={resetView}\n            >\n              <RotateCcw className=\"size-3.5\" />\n            </Button>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n              aria-label=\"Pan Right\"\n              onClick={() => panStep(15, 0)}\n            >\n              <ChevronRight className=\"size-4\" />\n            </Button>\n          </div>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Pan Down\"\n            onClick={() => panStep(0, -8)}\n          >\n            <ChevronDown className=\"size-4\" />\n          </Button>\n          <Separator className=\"my-0.5 bg-zinc-800\" />\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Zoom In\"\n            onClick={() => zoomStep(0.15)}\n          >\n            <ZoomIn className=\"size-3.5\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Zoom Out\"\n            onClick={() => zoomStep(-0.15)}\n          >\n            <ZoomOut className=\"size-3.5\" />\n          </Button>\n        </div>\n\n        {/* 2D Floor Plan Minimap Overlay */}\n        {isMinimapOpen && (\n          <TourFloorPlanNavigator\n            rooms={allRooms}\n            currentRoomId={currentRoomId}\n            currentRoom={currentRoom}\n            yaw={yaw}\n            onSelectRoom={switchRoom}\n            onClose={() => setIsMinimapOpen(false)}\n          />\n        )}\n\n        {/* Feature Inspection Floating Detail Card Modal */}\n        {activeInfoHotspot && (\n          <TourHotspotModal hotspot={activeInfoHotspot} onClose={() => setActiveInfoHotspot(null)} />\n        )}\n\n        {/* Bottom Floating Navigation Dock */}\n        <div className=\"pointer-events-auto absolute inset-x-3.5 bottom-3.5 z-30 flex items-center justify-center\">\n          <div className=\"flex max-w-full flex-wrap items-center justify-center gap-2 rounded-2xl border border-zinc-700/80 bg-zinc-950/90 p-2 shadow-sm backdrop-blur-xl sm:px-4\">\n            <Button\n              variant={isAutoRotating ? 'default' : 'secondary'}\n              size=\"sm\"\n              className=\"h-8 gap-1.5 rounded-full px-3 text-xs font-medium\"\n              aria-label={isAutoRotating ? 'Pause 360 auto rotate' : 'Play 360 auto rotate'}\n              onClick={() => setIsAutoRotating((prev) => !prev)}\n            >\n              {isAutoRotating ? <Pause className=\"size-3.5\" /> : <Play className=\"size-3.5\" />}\n              <span className=\"hidden sm:inline\">{isAutoRotating ? 'Pause Tour' : 'Auto Tour'}</span>\n            </Button>\n\n            <Separator orientation=\"vertical\" className=\"hidden h-5 bg-zinc-800 sm:block\" />\n\n            <div className=\"flex items-center gap-1.5 overflow-x-auto py-0.5\">\n              {allRooms.map((r) => {\n                const RoomIcon = getRoomIcon(r.id)\n                return (\n                  <button\n                    key={r.id}\n                    type=\"button\"\n                    className={`group focus-visible:ring-success flex items-center gap-2 rounded-xl px-2.5 py-1.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none ${\n                      currentRoomId === r.id\n                        ? 'bg-success text-white shadow-md'\n                        : 'border border-zinc-800 bg-zinc-900/90 text-zinc-300 hover:bg-zinc-800 hover:text-white'\n                    }`}\n                    onClick={() => switchRoom(r.id)}\n                  >\n                    <RoomIcon className=\"size-3.5\" />\n                    <span className=\"whitespace-nowrap\">{r.name}</span>\n                  </button>\n                )\n              })}\n            </div>\n\n            <Separator orientation=\"vertical\" className=\"hidden h-5 bg-zinc-800 sm:block\" />\n\n            <Button\n              variant={isMinimapOpen ? 'default' : 'secondary'}\n              size=\"sm\"\n              className=\"h-8 gap-1.5 rounded-full px-2.5 text-xs\"\n              aria-label={isMinimapOpen ? 'Hide floor plan minimap' : 'Show floor plan minimap'}\n              onClick={() => setIsMinimapOpen((prev) => !prev)}\n            >\n              <MapIcon className=\"size-3.5\" />\n              <span className=\"hidden md:inline\">Minimap</span>\n            </Button>\n\n            <Button\n              variant={isMeasurementMode ? 'destructive' : 'secondary'}\n              size=\"sm\"\n              className=\"h-8 gap-1.5 rounded-full px-2.5 text-xs\"\n              aria-label={isMeasurementMode ? 'Turn off measurement mode' : 'Turn on measurement mode'}\n              onClick={() => setIsMeasurementMode((prev) => !prev)}\n            >\n              <Ruler className=\"size-3.5\" />\n              <span className=\"hidden md:inline\">Measure</span>\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      {/* Architectural Deep Dive Specs & Floor Schedule Cards */}\n      <TourRoomDetails currentRoom={currentRoom} />\n    </div>\n  )\n}\n\nexport default VirtualTourPanorama\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/VirtualTourPanorama.tsx"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/TourFloorPlanNavigator.tsx",
      "content": "'use client'\n\nimport { Map as MapIcon, X } from 'lucide-react'\nimport { Button } from '@/components/ui/button'\nimport { type RoomData } from './virtual-tour-panorama-types'\n\nexport function TourFloorPlanNavigator({\n  rooms,\n  currentRoomId,\n  currentRoom,\n  yaw,\n  onSelectRoom,\n  onClose,\n}: {\n  rooms: RoomData[]\n  currentRoomId: string\n  currentRoom: RoomData\n  yaw: number\n  onSelectRoom: (roomId: string) => void\n  onClose: () => void\n}) {\n  return (\n    <>\n      {/* 2D Floor Plan Minimap Overlay (Top-Left corner dock) */}\n      <div className=\"pointer-events-auto absolute top-14 left-3.5 z-30 w-56 overflow-hidden rounded-xl border border-zinc-700/80 bg-zinc-950/90 shadow-sm backdrop-blur-md transition-colors sm:w-64\">\n        <div className=\"flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5\">\n          <div className=\"flex items-center gap-1.5\">\n            <MapIcon className=\"text-primary size-3.5\" />\n            <span className=\"text-xs font-bold text-zinc-200\">Floor Plan Minimap</span>\n          </div>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            className=\"text-muted-foreground size-5 hover:text-white\"\n            aria-label=\"Close minimap\"\n            onClick={() => onClose()}\n          >\n            <X className=\"size-3\" />\n          </Button>\n        </div>\n\n        <div className=\"relative p-2.5\">\n          {/* 2D CAD Blueprint Floor Plan SVG */}\n          <svg className=\"w-full\" viewBox=\"0 0 240 160\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <defs>\n              <radialGradient id=\"fovGradientReact\" cx=\"0\" cy=\"0\" r=\"100%\" gradientUnits=\"userSpaceOnUse\">\n                <stop offset=\"0%\" stopColor=\"#10b981\" stopOpacity=\"0.85\" />\n                <stop offset=\"60%\" stopColor=\"#10b981\" stopOpacity=\"0.35\" />\n                <stop offset=\"100%\" stopColor=\"#10b981\" stopOpacity=\"0.0\" />\n              </radialGradient>\n            </defs>\n\n            {/* Floor Plan Room Outlines */}\n            {rooms.map((r) => (\n              <g key={'map-r-' + r.id} className=\"cursor-pointer\" onClick={() => onSelectRoom(r.id)}>\n                <rect\n                  x={r.floorPlanCoords.x}\n                  y={r.floorPlanCoords.y}\n                  width={r.floorPlanCoords.w}\n                  height={r.floorPlanCoords.h}\n                  className={`transition-colors duration-200 ${\n                    currentRoomId === r.id\n                      ? 'fill-success/25 stroke-success stroke-2'\n                      : 'fill-zinc-900/80 stroke-zinc-700 stroke-1 hover:fill-zinc-800'\n                  }`}\n                  rx=\"3\"\n                />\n              </g>\n            ))}\n\n            {/* Dynamic Field of View (FOV) Radar Cone Indicator */}\n            <g\n              transform={`translate(${currentRoom.floorPlanCoords.cx}, ${currentRoom.floorPlanCoords.cy}) rotate(${yaw + 180})`}\n            >\n              <path d=\"M 0 0 L -22 -44 A 48 48 0 0 1 22 -44 Z\" fill=\"url(#fovGradientReact)\" />\n              <line x1=\"0\" y1=\"0\" x2=\"-22\" y2=\"-44\" stroke=\"var(--success)\" strokeWidth=\"1\" strokeDasharray=\"2 2\" />\n              <line x1=\"0\" y1=\"0\" x2=\"22\" y2=\"-44\" stroke=\"var(--success)\" strokeWidth=\"1\" strokeDasharray=\"2 2\" />\n            </g>\n\n            {/* Active Viewpoint Marker with Pulsing Ping */}\n            <circle\n              cx={currentRoom.floorPlanCoords.cx}\n              cy={currentRoom.floorPlanCoords.cy}\n              r=\"4\"\n              fill=\"var(--success)\"\n              stroke=\"#ffffff\"\n              strokeWidth=\"1.5\"\n            />\n\n            {/* Room Labels (painted last so the FOV cone and viewpoint marker never cover them) */}\n            {rooms.map((r) => (\n              <text\n                key={'map-l-' + r.id}\n                x={r.floorPlanCoords.cx}\n                y={r.floorPlanCoords.cy + 4}\n                textAnchor=\"middle\"\n                className={`pointer-events-none text-xs font-semibold select-none ${\n                  currentRoomId === r.id ? 'fill-success font-bold' : 'fill-muted-foreground'\n                }`}\n                style={{ fontSize: 9.5 }}\n              >\n                {r.floorPlanCoords.label}\n              </text>\n            ))}\n          </svg>\n\n          <div className=\"text-muted-foreground mt-1 flex items-center justify-between text-xs\">\n            <span className=\"font-mono\">North ↑</span>\n            <span>Click room zone to jump</span>\n          </div>\n        </div>\n      </div>\n    </>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/TourFloorPlanNavigator.tsx"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/TourHotspotModal.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { CheckCircle2, X } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { type RoomHotspot } from './virtual-tour-panorama-types'\n\nexport interface TourHotspotModalProps {\n  hotspot: RoomHotspot\n  onClose: () => void\n}\n\nexport function TourHotspotModal({ hotspot, onClose }: TourHotspotModalProps) {\n  if (!hotspot || !hotspot.infoData) return null\n\n  return (\n    <div className=\"border-info/40 pointer-events-auto absolute inset-x-4 top-14 z-40 mx-auto max-w-md rounded-xl border bg-zinc-950/95 p-4 shadow-sm backdrop-blur-xl sm:top-20\">\n      <div className=\"flex items-start justify-between gap-3 border-b border-zinc-800 pb-3\">\n        <div className=\"space-y-1\">\n          <div className=\"flex items-center gap-2\">\n            <Badge variant=\"outline\" className=\"border-info/50 bg-info/10 text-info text-xs font-semibold\">\n              {hotspot.infoData.category}\n            </Badge>\n            <span className=\"text-muted-foreground font-mono text-xs\">{hotspot.infoData.highlight}</span>\n          </div>\n          <h3 className=\"text-sm font-bold text-white sm:text-base\">{hotspot.title}</h3>\n        </div>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"text-muted-foreground size-7 hover:text-white\"\n          aria-label=\"Close feature modal\"\n          onClick={onClose}\n        >\n          <X className=\"size-4\" />\n        </Button>\n      </div>\n\n      <div className=\"space-y-3 pt-3 text-xs\">\n        <p className=\"leading-relaxed text-zinc-300\">{hotspot.infoData.description}</p>\n\n        <div className=\"space-y-1.5 rounded-lg border border-zinc-800 bg-zinc-900/60 p-2.5\">\n          <span className=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n            Architectural Specifications\n          </span>\n          <ul className=\"grid grid-cols-1 gap-1 text-zinc-200 sm:grid-cols-2\">\n            {hotspot.infoData.specs.map((spec, idx) => (\n              <li key={idx} className=\"flex items-center gap-1.5\">\n                <CheckCircle2 className=\"text-info size-3 shrink-0\" />\n                <span className=\"truncate\">{spec}</span>\n              </li>\n            ))}\n          </ul>\n        </div>\n\n        <div className=\"text-muted-foreground flex items-center justify-between border-t border-zinc-800 pt-2 text-xs\">\n          <span>\n            Finishes: <strong className=\"text-zinc-200\">{hotspot.infoData.material}</strong>\n          </span>\n          <Button size=\"sm\" className=\"h-7 text-xs\" onClick={onClose}>\n            Done\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/TourHotspotModal.tsx"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/TourPanoramaScene.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Ruler } from 'lucide-react'\nimport { type RoomData } from './virtual-tour-panorama-types'\n\nexport interface TourPanoramaSceneProps {\n  currentRoom: RoomData\n  yaw: number\n  pitch: number\n  zoom: number\n  isRoomTransitioning: boolean\n  isMeasurementMode: boolean\n  measurementUnit: 'imperial' | 'metric'\n}\n\nexport function TourPanoramaScene({\n  currentRoom,\n  yaw,\n  pitch,\n  zoom,\n  isRoomTransitioning,\n  isMeasurementMode,\n  measurementUnit,\n}: TourPanoramaSceneProps) {\n  return (\n    <div\n      className={`absolute inset-0 size-full transition-colors duration-300 ease-out ${\n        isRoomTransitioning ? 'scale-105 opacity-20 blur-md' : 'opacity-100 blur-none'\n      }`}\n      style={{\n        transform: `scale(${zoom})`,\n        transformOrigin: 'center center',\n      }}\n    >\n      {/* Background Gradient Ambience Layer */}\n      <div className={`absolute inset-0 bg-gradient-to-b ${currentRoom.skylineStyle}`} />\n\n      {/* Architectural Wireframe / Perspective Room Illustration */}\n      <svg\n        className=\"absolute inset-0 size-full\"\n        preserveAspectRatio=\"none\"\n        viewBox=\"0 0 1000 600\"\n        fill=\"none\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <defs>\n          <radialGradient id=\"sunGlowReact\" cx=\"50%\" cy=\"40%\" r=\"50%\">\n            <stop offset=\"0%\" stopColor=\"#fbbf24\" stopOpacity=\"0.35\" />\n            <stop offset=\"60%\" stopColor=\"#f43f5e\" stopOpacity=\"0.15\" />\n            <stop offset=\"100%\" stopColor=\"#000000\" stopOpacity=\"0\" />\n          </radialGradient>\n          <linearGradient id=\"floorGradReact\" x1=\"50%\" y1=\"0%\" x2=\"50%\" y2=\"100%\">\n            <stop offset=\"0%\" stopColor=\"#0f172a\" stopOpacity=\"0.9\" />\n            <stop offset=\"40%\" stopColor=\"#1e293b\" stopOpacity=\"0.95\" />\n            <stop offset=\"100%\" stopColor=\"#020617\" stopOpacity=\"1\" />\n          </linearGradient>\n          <linearGradient id=\"ceilingGradReact\" x1=\"50%\" y1=\"0%\" x2=\"50%\" y2=\"100%\">\n            <stop offset=\"0%\" stopColor=\"#020617\" />\n            <stop offset=\"100%\" stopColor=\"#0f172a\" stopOpacity=\"0.7\" />\n          </linearGradient>\n        </defs>\n\n        {/* Panoramic Window Skyline & Perspective Architecture (Parallax shifting with yaw) */}\n        <g transform={`translate(${-(yaw / 360) * 320}, ${-pitch * 2})`}>\n          {/* Horizon Skybox Glow */}\n          <rect x=\"-300\" y=\"80\" width=\"1600\" height=\"340\" fill=\"url(#sunGlowReact)\" />\n\n          {/* Skyline Cityscape Silhouettes */}\n          <path\n            d=\"M-200 320 L-180 260 L-150 260 L-150 320 L-120 320 L-110 220 L-80 220 L-70 320 L-40 320 L-30 200 L0 200 L10 320 L50 320 L60 170 L90 170 L100 320 L150 320 L160 240 L190 240 L200 320 L260 320 L270 190 L310 190 L320 320 L380 320 L390 210 L430 210 L440 320 L500 320 L510 160 L540 160 L560 320 L620 320 L630 230 L670 230 L680 320 L740 320 L750 180 L790 180 L800 320 L860 320 L870 250 L910 250 L920 320 L980 320 L990 200 L1030 200 L1040 320 L1100 320 L1120 230 L1160 230 L1180 320 L1240 320 L1250 190 L1290 190 L1300 320 Z\"\n            fill=\"#1e1b4b\"\n            fillOpacity=\"0.4\"\n          />\n          <path\n            d=\"M-150 320 L-140 280 L-110 280 L-100 320 L-50 320 L-40 240 L-10 240 L0 320 L70 320 L80 220 L120 220 L130 320 L210 320 L220 250 L250 250 L260 320 L340 320 L350 210 L380 210 L390 320 L460 320 L470 240 L500 240 L510 320 L580 320 L590 200 L630 200 L640 320 L710 320 L720 260 L750 260 L760 320 L830 320 L840 220 L880 220 L890 320 L960 320 L970 260 L1000 260 L1010 320 L1080 320 L1090 220 L1130 220 L1140 320 Z\"\n            fill=\"#0f172a\"\n            fillOpacity=\"0.65\"\n          />\n\n          {/* Architectural Window Mullions */}\n          <line x1=\"0\" y1=\"90\" x2=\"0\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"250\" y1=\"90\" x2=\"250\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"500\" y1=\"90\" x2=\"500\" y2=\"400\" stroke=\"#475569\" strokeWidth=\"8\" />\n          <line x1=\"750\" y1=\"90\" x2=\"750\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"1000\" y1=\"90\" x2=\"1000\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"1250\" y1=\"90\" x2=\"1250\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"-300\" y1=\"90\" x2=\"1300\" y2=\"90\" stroke=\"#334155\" strokeWidth=\"6\" />\n          <line x1=\"-300\" y1=\"400\" x2=\"1300\" y2=\"400\" stroke=\"#334155\" strokeWidth=\"8\" />\n\n          {/* Room-Specific Detailed Focal Art Piece */}\n          {currentRoom.id === 'master-bedroom' && (\n            <g>\n              {/* King Upholstered Bed & Acoustic Slat Headboard */}\n              <rect x=\"360\" y=\"310\" width=\"280\" height=\"90\" rx=\"4\" fill=\"#1e293b\" stroke=\"#475569\" strokeWidth=\"2\" />\n              <rect x=\"380\" y=\"240\" width=\"240\" height=\"70\" rx=\"6\" fill=\"#334155\" stroke=\"#64748b\" strokeWidth=\"2\" />\n              <line x1=\"400\" y1=\"240\" x2=\"400\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"420\" y1=\"240\" x2=\"420\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"440\" y1=\"240\" x2=\"440\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"460\" y1=\"240\" x2=\"460\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"480\" y1=\"240\" x2=\"480\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"500\" y1=\"240\" x2=\"500\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"520\" y1=\"240\" x2=\"520\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"540\" y1=\"240\" x2=\"540\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"560\" y1=\"240\" x2=\"560\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"580\" y1=\"240\" x2=\"580\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <line x1=\"600\" y1=\"240\" x2=\"600\" y2=\"310\" stroke=\"#475569\" strokeWidth=\"1.5\" />\n              <ellipse cx=\"430\" cy=\"330\" rx=\"35\" ry=\"15\" fill=\"#e2e8f0\" fillOpacity=\"0.8\" />\n              <ellipse cx=\"570\" cy=\"330\" rx=\"35\" ry=\"15\" fill=\"#e2e8f0\" fillOpacity=\"0.8\" />\n              <rect x=\"300\" y=\"340\" width=\"50\" height=\"45\" rx=\"3\" fill=\"#0f172a\" stroke=\"#334155\" />\n              <rect x=\"650\" y=\"340\" width=\"50\" height=\"45\" rx=\"3\" fill=\"#0f172a\" stroke=\"#334155\" />\n              <circle cx=\"325\" cy=\"315\" r=\"8\" fill=\"#f59e0b\" fillOpacity=\"0.6\" />\n              <circle cx=\"675\" cy=\"315\" r=\"8\" fill=\"#f59e0b\" fillOpacity=\"0.6\" />\n            </g>\n          )}\n\n          {currentRoom.id === 'living-room' && (\n            <g>\n              {/* Curved Sectional Sofa & Travertine Hearth */}\n              <path\n                d=\"M320 380 Q500 360 680 380 L660 430 Q500 410 340 430 Z\"\n                fill=\"#334155\"\n                stroke=\"#64748b\"\n                strokeWidth=\"2\"\n              />\n              <rect x=\"420\" y=\"400\" width=\"160\" height=\"40\" rx=\"8\" fill=\"#475569\" stroke=\"#94a3b8\" strokeWidth=\"1.5\" />\n              <rect x=\"400\" y=\"270\" width=\"200\" height=\"35\" rx=\"4\" fill=\"#09090b\" stroke=\"#f59e0b\" strokeWidth=\"1.5\" />\n              <path\n                d=\"M420 292 Q430 280 440 292 Q450 276 460 292 Q470 282 480 292 Q490 278 500 292 Q510 280 520 292 Q530 276 540 292 Q550 280 560 292 Q570 278 580 292\"\n                stroke=\"#f97316\"\n                strokeWidth=\"3\"\n                fill=\"none\"\n              />\n            </g>\n          )}\n\n          {currentRoom.id === 'gourmet-kitchen' && (\n            <g>\n              {/* 12ft Waterfall Island & High Barstools */}\n              <path\n                d=\"M300 340 L700 340 L740 420 L260 420 Z\"\n                fill=\"#0f172a\"\n                stroke=\"#38bdf8\"\n                strokeWidth=\"2\"\n                strokeDasharray=\"400 4\"\n              />\n              <rect x=\"360\" y=\"325\" width=\"280\" height=\"15\" rx=\"3\" fill=\"#1e293b\" stroke=\"#64748b\" />\n              <circle cx=\"340\" cy=\"425\" r=\"14\" fill=\"#334155\" stroke=\"#94a3b8\" />\n              <circle cx=\"450\" cy=\"425\" r=\"14\" fill=\"#334155\" stroke=\"#94a3b8\" />\n              <circle cx=\"550\" cy=\"425\" r=\"14\" fill=\"#334155\" stroke=\"#94a3b8\" />\n              <circle cx=\"660\" cy=\"425\" r=\"14\" fill=\"#334155\" stroke=\"#94a3b8\" />\n            </g>\n          )}\n\n          {currentRoom.id === 'rooftop-terrace' && (\n            <g>\n              {/* Infinity Plunge Pool & Lounges */}\n              <rect x=\"520\" y=\"340\" width=\"340\" height=\"90\" rx=\"8\" fill=\"#0369a1\" stroke=\"#38bdf8\" strokeWidth=\"3\" />\n              <path\n                d=\"M540 370 Q600 360 660 370 Q720 380 780 370 Q820 360 840 370\"\n                stroke=\"#7dd3fc\"\n                strokeWidth=\"2\"\n                fill=\"none\"\n              />\n              <path\n                d=\"M540 395 Q600 385 660 395 Q720 405 780 395 Q820 385 840 395\"\n                stroke=\"#7dd3fc\"\n                strokeWidth=\"2\"\n                fill=\"none\"\n              />\n              <rect x=\"220\" y=\"360\" width=\"100\" height=\"40\" rx=\"4\" fill=\"#78350f\" stroke=\"#b45309\" strokeWidth=\"1.5\" />\n              <rect x=\"350\" y=\"360\" width=\"100\" height=\"40\" rx=\"4\" fill=\"#78350f\" stroke=\"#b45309\" strokeWidth=\"1.5\" />\n            </g>\n          )}\n\n          {currentRoom.id === 'spa-bath' && (\n            <g>\n              {/* Freestanding Oval Soaking Tub & Dual Mirror Wall */}\n              <ellipse cx=\"500\" cy=\"380\" rx=\"140\" ry=\"45\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" strokeWidth=\"3\" />\n              <ellipse cx=\"500\" cy=\"380\" rx=\"115\" ry=\"32\" fill=\"#0284c7\" fillOpacity=\"0.4\" />\n              <circle cx=\"340\" cy=\"240\" r=\"45\" fill=\"#1e293b\" stroke=\"#f59e0b\" strokeWidth=\"2\" />\n              <circle cx=\"660\" cy=\"240\" r=\"45\" fill=\"#1e293b\" stroke=\"#f59e0b\" strokeWidth=\"2\" />\n            </g>\n          )}\n        </g>\n\n        {/* Perspective Floor Grid */}\n        <path\n          d=\"M-200 600 L300 400 M0 600 L400 400 M200 600 L500 400 M400 600 L500 400 M600 600 L500 400 M800 600 L600 400 M1000 600 L700 400 M1200 600 L800 400\"\n          stroke=\"#334155\"\n          strokeOpacity=\"0.25\"\n          strokeWidth=\"1.5\"\n        />\n        <line x1=\"-200\" y1=\"440\" x2=\"1200\" y2=\"440\" stroke=\"#334155\" strokeOpacity=\"0.25\" strokeWidth=\"1.5\" />\n        <line x1=\"-200\" y1=\"490\" x2=\"1200\" y2=\"490\" stroke=\"#334155\" strokeOpacity=\"0.25\" strokeWidth=\"1.5\" />\n        <line x1=\"-200\" y1=\"545\" x2=\"1200\" y2=\"545\" stroke=\"#334155\" strokeOpacity=\"0.25\" strokeWidth=\"1.5\" />\n\n        {/* Ceiling Cove Recessed Glow line */}\n        <line x1=\"0\" y1=\"40\" x2=\"1000\" y2=\"40\" stroke=\"#fbbf24\" strokeOpacity=\"0.3\" strokeWidth=\"2\" />\n      </svg>\n\n      {/* Dynamic Laser Measurement Overlay (When enabled) */}\n      {isMeasurementMode && (\n        <svg\n          className=\"pointer-events-none absolute inset-0 size-full transition-opacity duration-300\"\n          preserveAspectRatio=\"none\"\n          viewBox=\"0 0 100 100\"\n        >\n          {currentRoom.measurements.map((m) => (\n            <g key={m.id}>\n              <line\n                x1={m.x1}\n                y1={m.y1}\n                x2={m.x2}\n                y2={m.y2}\n                stroke=\"#f43f5e\"\n                strokeWidth=\"0.75\"\n                strokeDasharray=\"2 1.5\"\n              />\n              <circle cx={m.x1} cy={m.y1} r=\"1.5\" fill=\"#f43f5e\" />\n              <circle cx={m.x2} cy={m.y2} r=\"1.5\" fill=\"#f43f5e\" />\n            </g>\n          ))}\n        </svg>\n      )}\n\n      {/* Measurement Pill Badges (HTML Overlay) */}\n      {isMeasurementMode && (\n        <div className=\"pointer-events-none absolute inset-0 size-full\">\n          {currentRoom.measurements.map((m) => (\n            <div\n              key={'badge-' + m.id}\n              className=\"absolute -translate-x-1/2 -translate-y-1/2\"\n              style={{ left: `${m.labelX}%`, top: `${m.labelY}%` }}\n            >\n              <div className=\"border-destructive/80 text-destructive flex items-center gap-1.5 rounded-md border bg-zinc-950/90 px-2 py-0.5 font-mono text-xs font-bold shadow-xl backdrop-blur-md\">\n                <Ruler className=\"size-3\" />\n                <span>{m.title}:</span>\n                <span className=\"text-white\">{measurementUnit === 'imperial' ? m.imperial : m.metric}</span>\n              </div>\n            </div>\n          ))}\n        </div>\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/TourPanoramaScene.tsx"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/TourRoomDetails.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, Bath, Bed, Calendar, Home, ShieldCheck, Sofa, Sun, Utensils } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { type RoomData } from './virtual-tour-panorama-types'\n\nexport interface TourRoomDetailsProps {\n  currentRoom: RoomData\n}\n\nfunction getRoomIcon(roomId: string) {\n  switch (roomId) {\n    case 'master-bedroom':\n      return Bed\n    case 'living-room':\n      return Sofa\n    case 'gourmet-kitchen':\n      return Utensils\n    case 'rooftop-terrace':\n      return Sun\n    case 'spa-bath':\n      return Bath\n    default:\n      return Home\n  }\n}\n\nexport function TourRoomDetails({ currentRoom }: TourRoomDetailsProps) {\n  const RoomIcon = getRoomIcon(currentRoom.id)\n\n  return (\n    <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-12\">\n      {/* Active Room Overview Card */}\n      <Card className=\"shadow-xs lg:col-span-8\">\n        <CardHeader className=\"pb-3\">\n          <div className=\"flex flex-wrap items-center justify-between gap-2\">\n            <div>\n              <CardTitle className=\"flex items-center gap-2 text-base font-bold sm:text-lg\">\n                <RoomIcon className=\"text-primary size-5\" />\n                <span>{currentRoom.badgeName}</span>\n              </CardTitle>\n              <CardDescription className=\"text-xs\">\n                {currentRoom.exposure} · {currentRoom.floor} · Verified Architectural Plan\n              </CardDescription>\n            </div>\n            <Badge variant=\"outline\" className=\"font-mono text-xs\">\n              {currentRoom.area}\n            </Badge>\n          </div>\n        </CardHeader>\n        <CardContent className=\"space-y-4\">\n          <p className=\"text-muted-foreground text-xs leading-relaxed sm:text-sm\">{currentRoom.description}</p>\n\n          {/* Room Architectural Metrics Grid */}\n          <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n            <div className=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n              <span className=\"text-muted-foreground block\">Ceiling Height</span>\n              <span className=\"text-foreground font-mono font-bold\">{currentRoom.ceilingHeight}</span>\n            </div>\n            <div className=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n              <span className=\"text-muted-foreground block\">Floor Area</span>\n              <span className=\"text-foreground font-mono font-bold\">{currentRoom.area.split(' ')[0]} sq ft</span>\n            </div>\n            <div className=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n              <span className=\"text-muted-foreground block\">Solar Aspect</span>\n              <span className=\"text-foreground font-semibold\">{currentRoom.exposure.split(' ')[0]}</span>\n            </div>\n            <div className=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n              <span className=\"text-muted-foreground block\">Tour Hotspots</span>\n              <span className=\"text-foreground font-mono font-bold\">{currentRoom.hotspots.length} Viewpoints</span>\n            </div>\n          </div>\n\n          {/* Feature Bullet List */}\n          <div className=\"space-y-2 border-t pt-3\">\n            <h4 className=\"text-muted-foreground text-xs font-bold tracking-wider uppercase\">\n              Finishes & Smart Building Integration\n            </h4>\n            <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n              {currentRoom.features.map((f, i) => (\n                <div key={i} className=\"bg-muted/20 flex items-start gap-2 rounded-md border p-2 text-xs\">\n                  <ShieldCheck className=\"text-success mt-0.5 size-3.5 shrink-0\" />\n                  <div>\n                    <span className=\"text-muted-foreground font-medium\">{f.label}: </span>\n                    <span className=\"text-foreground font-semibold\">{f.value}</span>\n                  </div>\n                </div>\n              ))}\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n\n      {/* Penthouse Residence Quick Facts & Viewing Inquire Card */}\n      <Card className=\"shadow-xs lg:col-span-4\">\n        <CardHeader className=\"pb-3\">\n          <CardTitle className=\"text-base font-bold\">Residence 14B Specs</CardTitle>\n          <CardDescription className=\"text-xs\">The Evergreen Penthouse Collection</CardDescription>\n        </CardHeader>\n        <CardContent className=\"space-y-3.5 text-xs\">\n          <div className=\"space-y-2\">\n            <div className=\"flex items-center justify-between border-b pb-1.5\">\n              <span className=\"text-muted-foreground\">Total Living Area</span>\n              <span className=\"font-mono font-bold\">2,270 sq ft (210.8 m²)</span>\n            </div>\n            <div className=\"flex items-center justify-between border-b pb-1.5\">\n              <span className=\"text-muted-foreground\">Bedrooms / Baths</span>\n              <span className=\"font-semibold\">3 Bed · 3.5 Bath</span>\n            </div>\n            <div className=\"flex items-center justify-between border-b pb-1.5\">\n              <span className=\"text-muted-foreground\">Private Outdoor Sky Deck</span>\n              <span className=\"font-mono font-bold\">540 sq ft (Level 15)</span>\n            </div>\n            <div className=\"flex items-center justify-between border-b pb-1.5\">\n              <span className=\"text-muted-foreground\">Dedicated Valet EV Parking</span>\n              <span className=\"font-semibold\">2 Stalls (Level P1)</span>\n            </div>\n            <div className=\"flex items-center justify-between\">\n              <span className=\"text-muted-foreground\">Monthly HOA & Concierge</span>\n              <span className=\"font-mono font-bold\">$1,840 / mo</span>\n            </div>\n          </div>\n\n          <div className=\"bg-primary/5 border-primary/20 space-y-1 rounded-lg border p-3\">\n            <div className=\"text-primary flex items-center gap-1.5 font-semibold\">\n              <Calendar className=\"size-3.5\" />\n              <span>Private VIP Walkthroughs</span>\n            </div>\n            <p className=\"text-muted-foreground text-xs leading-relaxed\">\n              In-person appointments available Tuesdays & Thursdays by private broker registration.\n            </p>\n          </div>\n        </CardContent>\n        <CardFooter className=\"pt-0\">\n          <Button className=\"w-full gap-2 text-xs font-semibold shadow-xs\">\n            <span>Schedule In-Person Penthouse Tour</span>\n            <ArrowRight className=\"size-3.5\" />\n          </Button>\n        </CardFooter>\n      </Card>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/TourRoomDetails.tsx"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/virtual-tour-panorama-data.ts",
      "content": "import type { RoomData } from './virtual-tour-panorama-types'\n\nexport const defaultRooms: RoomData[] = [\n  {\n    id: 'master-bedroom',\n    name: 'Master Bedroom',\n    badgeName: 'Master Penthouse Bedroom',\n    floor: 'Level 14',\n    area: '450 sq ft (41.8 m²)',\n    exposure: 'South-East Panoramic',\n    ceilingHeight: '10.5 ft (3.20 m)',\n    description:\n      'Expansive private retreat with floor-to-ceiling soundproof glazing, custom Italian walnut millwork, motorized blackout drapes, and direct access to both the spa en-suite and sky terrace.',\n    skylineStyle: 'from-indigo-950 via-slate-900 to-amber-950/40',\n    floorPlanCoords: { cx: 178, cy: 46, x: 125, y: 10, w: 105, h: 72, label: 'Master Bed' },\n    hotspots: [\n      {\n        id: 'hs-bed-to-bath',\n        type: 'portal',\n        x: 28,\n        y: 56,\n        title: 'Walk to En-suite Bathroom →',\n        subtitle: 'Master Spa Bath · 10 ft away',\n        targetRoomId: 'spa-bath',\n      },\n      {\n        id: 'hs-bed-to-terrace',\n        type: 'portal',\n        x: 78,\n        y: 52,\n        title: 'Step out to Balcony →',\n        subtitle: 'Skyline Rooftop Terrace · 14 ft away',\n        targetRoomId: 'rooftop-terrace',\n      },\n      {\n        id: 'hs-bed-wardrobe',\n        type: 'info',\n        x: 14,\n        y: 44,\n        title: 'Inspect Walk-in Wardrobe',\n        subtitle: 'Poliform custom cabinetry · Biometric safe',\n        infoData: {\n          category: 'Joinery & Storage',\n          specs: [\n            'Italian smoked walnut finish',\n            'Integrated 3000K sensor LED rails',\n            'Velvet-lined jewelry vitrines',\n            'Biometric concealed safe',\n          ],\n          description:\n            'Custom fitted Italian dressing suite designed by Poliform with motion-activated illumination and climate-regulated storage.',\n          highlight: '120 sq ft custom dressing suite',\n          material: 'Smoked Walnut & Velvet',\n        },\n      },\n      {\n        id: 'hs-bed-acoustic',\n        type: 'info',\n        x: 52,\n        y: 40,\n        title: 'Inspect King Headboard & Shades',\n        subtitle: 'Lutron motorized shades · Soundproofing',\n        infoData: {\n          category: 'Acoustics & Automation',\n          specs: [\n            'NRC 0.85 acoustic dampening',\n            'Lutron Sivoia QS dual blackout shades',\n            'Integrated USB-C & Qi fast charging',\n            'Solid white oak fluted slats',\n          ],\n          description:\n            'Architectural acoustic slatted wall treatment behind king suite paired with dual motorized solar and blackout drapery.',\n          highlight: 'Lutron HomeWorks Integrated',\n          material: 'White Oak & Bouclé Fabric',\n        },\n      },\n    ],\n    measurements: [\n      {\n        id: 'm-bed-ceiling',\n        title: 'Ceiling Height',\n        imperial: '10.5 ft',\n        metric: '3.20 m',\n        x1: 50,\n        y1: 18,\n        x2: 50,\n        y2: 82,\n        labelX: 52,\n        labelY: 48,\n      },\n      {\n        id: 'm-bed-span',\n        title: 'Room Span',\n        imperial: '24.8 ft',\n        metric: '7.56 m',\n        x1: 16,\n        y1: 76,\n        x2: 84,\n        y2: 76,\n        labelX: 50,\n        labelY: 80,\n      },\n      {\n        id: 'm-bed-window',\n        title: 'Window Wall',\n        imperial: '14.0 ft',\n        metric: '4.27 m',\n        x1: 64,\n        y1: 30,\n        x2: 92,\n        y2: 30,\n        labelX: 78,\n        labelY: 26,\n      },\n    ],\n    features: [\n      { label: 'Flooring', value: 'Wide-plank European White Oak (Herringbone)' },\n      { label: 'Soundproofing', value: 'STC 58 Acoustic Double Glazing' },\n      { label: 'Climate Zone', value: 'Independent Nest Pro Heat Pump Zone' },\n      { label: 'Lighting', value: 'Warm-Dim 2200K-3000K Architectural Recessed' },\n    ],\n  },\n  {\n    id: 'living-room',\n    name: 'Living Room',\n    badgeName: 'Grand Living Salon',\n    floor: 'Level 14',\n    area: '680 sq ft (63.2 m²)',\n    exposure: 'South-West Horizon',\n    ceilingHeight: '11.2 ft (3.41 m)',\n    description:\n      'Open-concept grand entertainment salon with double-height volume, continuous Calacatta marble slab flooring, custom dual-sided linear bioethanol fireplace, and panoramic sunset skyline vistas.',\n    skylineStyle: 'from-amber-950/40 via-purple-950 to-zinc-950',\n    floorPlanCoords: { cx: 65, cy: 46, x: 10, y: 10, w: 108, h: 72, label: 'Living Salon' },\n    hotspots: [\n      {\n        id: 'hs-liv-to-kit',\n        type: 'portal',\n        x: 22,\n        y: 54,\n        title: 'Enter Gourmet Kitchen →',\n        subtitle: 'Chef prep & dining · 8 ft away',\n        targetRoomId: 'gourmet-kitchen',\n      },\n      {\n        id: 'hs-liv-to-bed',\n        type: 'portal',\n        x: 82,\n        y: 56,\n        title: 'Go to Master Bedroom →',\n        subtitle: 'Master Penthouse Wing · 16 ft away',\n        targetRoomId: 'master-bedroom',\n      },\n      {\n        id: 'hs-liv-fireplace',\n        type: 'info',\n        x: 50,\n        y: 64,\n        title: 'Inspect Linear Fireplace Hearth',\n        subtitle: 'Calacatta gold marble · Eco-bioethanol',\n        infoData: {\n          category: 'Architectural Feature',\n          specs: [\n            '72-inch Planika bioethanol burner',\n            'Full bookmatched Calacatta slab',\n            'Zero-emission clean burn technology',\n            'Smartphone & remote flame modulation',\n          ],\n          description:\n            'Dramatic 72-inch dual-sided architectural fireplace framed in hand-selected bookmatched Italian Calacatta gold marble.',\n          highlight: 'Remote Flame Modulation',\n          material: 'Bookmatched Calacatta Gold',\n        },\n      },\n      {\n        id: 'hs-liv-audio',\n        type: 'info',\n        x: 68,\n        y: 38,\n        title: 'Architectural Audio & Smart Hub',\n        subtitle: 'Bang & Olufsen · Crestron Touchpanel',\n        infoData: {\n          category: 'Smart Home & Audio',\n          specs: [\n            '6x B&O flush architectural transducers',\n            'Crestron 10.1” wall touch interface',\n            '4K ultra-short laser cinema ready',\n            'Multi-zone scene preset lighting',\n          ],\n          description:\n            'Invisible architectural sound system with studio acoustic calibration and centralized whole-home automation.',\n          highlight: 'Whole-Home B&O Acoustic Sync',\n          material: 'Titanium & Acoustical Plaster',\n        },\n      },\n    ],\n    measurements: [\n      {\n        id: 'm-liv-ceiling',\n        title: 'Double-Height Ceiling',\n        imperial: '11.2 ft',\n        metric: '3.41 m',\n        x1: 50,\n        y1: 14,\n        x2: 50,\n        y2: 84,\n        labelX: 52,\n        labelY: 46,\n      },\n      {\n        id: 'm-liv-span',\n        title: 'Main Salon Span',\n        imperial: '32.0 ft',\n        metric: '9.75 m',\n        x1: 12,\n        y1: 78,\n        x2: 88,\n        y2: 78,\n        labelX: 50,\n        labelY: 82,\n      },\n      {\n        id: 'm-liv-slider',\n        title: 'Terrace Pocket Glazing',\n        imperial: '16.0 ft',\n        metric: '4.88 m',\n        x1: 60,\n        y1: 30,\n        x2: 94,\n        y2: 30,\n        labelX: 77,\n        labelY: 26,\n      },\n    ],\n    features: [\n      { label: 'Flooring', value: 'Honed Calacatta Marble with Inset Wool Rug' },\n      { label: 'Fireplace', value: 'Planika 72\" Automatic Bioethanol Hearth' },\n      { label: 'Glazing', value: 'Motorized Sliding Minimal Pocket Doors' },\n      { label: 'AV Hub', value: 'Bang & Olufsen Architectural Ceiling Sync' },\n    ],\n  },\n  {\n    id: 'gourmet-kitchen',\n    name: 'Gourmet Kitchen',\n    badgeName: \"Chef's Gourmet Kitchen\",\n    floor: 'Level 14',\n    area: '320 sq ft (29.7 m²)',\n    exposure: 'North-East Morning Light',\n    ceilingHeight: '10.5 ft (3.20 m)',\n    description:\n      'Culinary centerpiece anchored by a 12-foot honed quartzite waterfall island, seamless matte charcoal cabinetry, integrated Gaggenau 400 Series cooking appliances, and temperature-controlled wine storage.',\n    skylineStyle: 'from-slate-900 via-zinc-900 to-emerald-950/30',\n    floorPlanCoords: { cx: 42, cy: 122, x: 10, y: 92, w: 65, h: 58, label: 'Kitchen' },\n    hotspots: [\n      {\n        id: 'hs-kit-to-liv',\n        type: 'portal',\n        x: 80,\n        y: 54,\n        title: 'Return to Living Room →',\n        subtitle: 'Grand Salon · 8 ft away',\n        targetRoomId: 'living-room',\n      },\n      {\n        id: 'hs-kit-island',\n        type: 'info',\n        x: 46,\n        y: 60,\n        title: 'Inspect Waterfall Quartzite Island',\n        subtitle: '12ft Honed Nuvolato quartzite · Downdraft induction',\n        infoData: {\n          category: 'Countertops & Prep',\n          specs: [\n            'Seamless bookmatched mitered edges',\n            'Gaggenau induction with downdraft ventilation',\n            'Concealed power docks with fast Qi charging',\n            'Counter-height bar seating for four',\n          ],\n          description:\n            'Monolithic 12-foot island sculpted from rare Brazilian Nuvolato quartzite with integrated smart touch induction surface.',\n          highlight: '12-Foot Monolithic Slab',\n          material: 'Honed Nuvolato Quartzite',\n        },\n      },\n      {\n        id: 'hs-kit-appliances',\n        type: 'info',\n        x: 20,\n        y: 46,\n        title: 'Appliance Suite & Wine Reserve',\n        subtitle: 'Gaggenau 400 & Sub-Zero column · 102 bottles',\n        infoData: {\n          category: 'Professional Appliances',\n          specs: [\n            'Gaggenau 400 Series combi-steam & pyrolytic ovens',\n            'Sub-Zero 36” refrigeration column',\n            'Sub-Zero dual-zone 102-bottle wine vault',\n            'Miele Knock2Open fully integrated dishwasher',\n          ],\n          description:\n            'Fully integrated chef-grade culinary appliances with custom paneled facades and Sommelier wine preservation cellaring.',\n          highlight: 'Gaggenau 400 Collection',\n          material: 'Matte Charcoal & Fluted Bronze Glass',\n        },\n      },\n    ],\n    measurements: [\n      {\n        id: 'm-kit-ceiling',\n        title: 'Ceiling Height',\n        imperial: '10.5 ft',\n        metric: '3.20 m',\n        x1: 50,\n        y1: 18,\n        x2: 50,\n        y2: 82,\n        labelX: 52,\n        labelY: 48,\n      },\n      {\n        id: 'm-kit-island',\n        title: 'Quartzite Island Span',\n        imperial: '12.0 ft',\n        metric: '3.66 m',\n        x1: 28,\n        y1: 72,\n        x2: 72,\n        y2: 72,\n        labelX: 50,\n        labelY: 76,\n      },\n      {\n        id: 'm-kit-cabinet',\n        title: 'Custom Cabinetry Run',\n        imperial: '18.5 ft',\n        metric: '5.64 m',\n        x1: 14,\n        y1: 36,\n        x2: 58,\n        y2: 36,\n        labelX: 36,\n        labelY: 32,\n      },\n    ],\n    features: [\n      { label: 'Cabinetry', value: 'Custom Matte Fenix Soft-Close Charcoal' },\n      { label: 'Appliances', value: 'Gaggenau 400 Series + Sub-Zero Columns' },\n      { label: 'Wine Storage', value: '102-Bottle Dual Temperature Cellar' },\n      { label: 'Island', value: '12ft Nuvolato Quartzite Waterfall' },\n    ],\n  },\n  {\n    id: 'rooftop-terrace',\n    name: 'Rooftop Terrace',\n    badgeName: 'Skyline Rooftop Terrace',\n    floor: 'Level 15 (Private Rooftop)',\n    area: '540 sq ft (50.2 m²)',\n    exposure: '360° Panoramic Skyline',\n    ceilingHeight: 'Open Air (Skyline)',\n    description:\n      'Private top-tier sky oasis featuring heated saltwater infinity plunge pool, frameless tempered glass balustrades, Lynx outdoor kitchen BBQ suite, and 360-degree unobstructed horizon skyline.',\n    skylineStyle: 'from-blue-950/80 via-slate-900 to-indigo-950',\n    floorPlanCoords: { cx: 185, cy: 122, x: 140, y: 92, w: 90, h: 58, label: 'Sky Terrace' },\n    hotspots: [\n      {\n        id: 'hs-ter-to-bed',\n        type: 'portal',\n        x: 20,\n        y: 56,\n        title: 'Enter Master Bedroom →',\n        subtitle: 'Master Penthouse Suite · 14 ft away',\n        targetRoomId: 'master-bedroom',\n      },\n      {\n        id: 'hs-ter-to-bath',\n        type: 'portal',\n        x: 38,\n        y: 52,\n        title: 'Explore Spa Bath →',\n        subtitle: 'Spa En-suite · 18 ft away',\n        targetRoomId: 'spa-bath',\n      },\n      {\n        id: 'hs-ter-pool',\n        type: 'info',\n        x: 74,\n        y: 64,\n        title: 'Inspect Infinity Plunge Pool',\n        subtitle: 'Heated saltwater · Underwater LED · Counter-current',\n        infoData: {\n          category: 'Aquatics & Wellness',\n          specs: [\n            '14ft × 8ft heated infinity edge',\n            'Automated saltwater electrolytic chlorination',\n            'Fastlane counter-current swim jet module',\n            'Color-tunable RGBW underwater fiber optics',\n          ],\n          description:\n            'Custom cantilevered infinity-edge plunge pool with panoramic city views, integrated spa massage jets, and year-round automated heating.',\n          highlight: 'Heated Infinity Plunge Pool',\n          material: 'Custom Glass Mosaic & Teak Decking',\n        },\n      },\n      {\n        id: 'hs-ter-bbq',\n        type: 'info',\n        x: 52,\n        y: 48,\n        title: 'Outdoor Kitchen & BBQ Lounge',\n        subtitle: 'Lynx Sedona Grill · Teak joinery · Granite bar',\n        infoData: {\n          category: 'Outdoor Entertaining',\n          specs: [\n            'Lynx 36” pro sear gas grill',\n            'Marine-grade 316 stainless cabinetry',\n            'Under-counter beverage cooler & ice maker',\n            'Motorized louvered pergola canopy',\n          ],\n          description:\n            'All-weather al fresco culinary station with marine-grade steel, weatherproof teak millwork, and automated rain-sensing pergola.',\n          highlight: 'Lynx Pro Sear Stainless',\n          material: '316 Marine Steel & Natural Teak',\n        },\n      },\n    ],\n    measurements: [\n      {\n        id: 'm-ter-span',\n        title: 'Sky Deck Span',\n        imperial: '36.0 ft',\n        metric: '10.97 m',\n        x1: 10,\n        y1: 76,\n        x2: 90,\n        y2: 76,\n        labelX: 50,\n        labelY: 80,\n      },\n      {\n        id: 'm-ter-pool',\n        title: 'Plunge Pool Width',\n        imperial: '14.0 ft',\n        metric: '4.27 m',\n        x1: 62,\n        y1: 58,\n        x2: 88,\n        y2: 58,\n        labelX: 75,\n        labelY: 62,\n      },\n      {\n        id: 'm-ter-balustrade',\n        title: 'Glass Balustrade Height',\n        imperial: '4.2 ft',\n        metric: '1.28 m',\n        x1: 88,\n        y1: 44,\n        x2: 88,\n        y2: 72,\n        labelX: 84,\n        labelY: 58,\n      },\n    ],\n    features: [\n      { label: 'Decking', value: 'Sustainably Harvested Burmese Marine Teak' },\n      { label: 'Pool Type', value: 'Heated Saltwater Infinity Edge with Swim Jet' },\n      { label: 'Outdoor BBQ', value: 'Lynx Pro 36\" Infrared Gas Rotisserie' },\n      { label: 'Pergola', value: 'Renson Automated Motorized Louvers' },\n    ],\n  },\n  {\n    id: 'spa-bath',\n    name: 'Spa Bath',\n    badgeName: 'Master Spa Bath',\n    floor: 'Level 14',\n    area: '280 sq ft (26.0 m²)',\n    exposure: 'Private Zen Courtyard',\n    ceilingHeight: '10.5 ft (3.20 m)',\n    description:\n      'Serene stone retreat with bookmatched Fior di Bosco marble, freestanding Boffi soaking tub, dual floating vanities with brushed brass Dornbracht fixtures, and glass-enclosed thermostatic steam rain shower.',\n    skylineStyle: 'from-zinc-900 via-neutral-900 to-amber-950/30',\n    floorPlanCoords: { cx: 107, cy: 122, x: 80, y: 92, w: 55, h: 58, label: 'Spa Bath' },\n    hotspots: [\n      {\n        id: 'hs-bath-to-bed',\n        type: 'portal',\n        x: 78,\n        y: 56,\n        title: 'Return to Master Bedroom →',\n        subtitle: 'Master Penthouse Suite · 10 ft away',\n        targetRoomId: 'master-bedroom',\n      },\n      {\n        id: 'hs-bath-to-terrace',\n        type: 'portal',\n        x: 22,\n        y: 52,\n        title: 'Step out to Balcony →',\n        subtitle: 'Rooftop Terrace · 18 ft away',\n        targetRoomId: 'rooftop-terrace',\n      },\n      {\n        id: 'hs-bath-tub',\n        type: 'info',\n        x: 50,\n        y: 62,\n        title: 'Inspect Boffi Freestanding Tub',\n        subtitle: 'Sculpted Cristalplant stone · Floor-mounted Dornbracht',\n        infoData: {\n          category: 'Sanitaryware & Bath',\n          specs: [\n            'Boffi Iceland oval matte stone tub',\n            'Dornbracht MEM floor-mount thermostatic mixer',\n            'Integrated gentle chromotherapy glow',\n            'Adjacent skyline picture view window',\n          ],\n          description:\n            'Sculptural freestanding oval tub crafted from solid matte Cristalplant stone with dedicated floor-mounted brushed brass mixer.',\n          highlight: 'Boffi Designer Soaking Tub',\n          material: 'Cristalplant & Brushed Brass',\n        },\n      },\n      {\n        id: 'hs-bath-shower',\n        type: 'info',\n        x: 26,\n        y: 44,\n        title: 'Thermostatic Steam Rain Shower',\n        subtitle: 'Dual rain heads · Steam generator · Heated teak bench',\n        infoData: {\n          category: 'Hydrotherapy Suite',\n          specs: [\n            'Kaldewei thermostatic steam generator (45°C)',\n            'Dual 16” flush ceiling rain shower modules',\n            'Frameless anti-fog heated glass enclosure',\n            'Slip-resistant fluted marble base with radiant heating',\n          ],\n          description:\n            'Spa-grade steam enclosure with multi-jet hydrotherapy, Scottish rain shower simulator, and heated ergonomic teak bench.',\n          highlight: 'Dual Steam & Rain Hydrotherapy',\n          material: 'Fior di Bosco Marble & Heated Glass',\n        },\n      },\n    ],\n    measurements: [\n      {\n        id: 'm-bath-ceiling',\n        title: 'Ceiling Height',\n        imperial: '10.5 ft',\n        metric: '3.20 m',\n        x1: 50,\n        y1: 18,\n        x2: 50,\n        y2: 82,\n        labelX: 52,\n        labelY: 48,\n      },\n      {\n        id: 'm-bath-vanity',\n        title: 'Dual Floating Vanity',\n        imperial: '9.0 ft',\n        metric: '2.74 m',\n        x1: 58,\n        y1: 64,\n        x2: 90,\n        y2: 64,\n        labelX: 74,\n        labelY: 68,\n      },\n      {\n        id: 'm-bath-shower',\n        title: 'Steam Enclosure',\n        imperial: '7.5 ft',\n        metric: '2.28 m',\n        x1: 16,\n        y1: 38,\n        x2: 38,\n        y2: 38,\n        labelX: 27,\n        labelY: 34,\n      },\n    ],\n    features: [\n      { label: 'Stone', value: 'Full-slab Italian Fior di Bosco Marble' },\n      { label: 'Fixtures', value: 'Dornbracht MEM Series in Brushed Durabrass' },\n      { label: 'Tub', value: 'Boffi Iceland Cristalplant Matte Stone Soaker' },\n      { label: 'Radiant Heat', value: 'NuHeat Thermostatic In-Floor Heating' },\n    ],\n  },\n]\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/virtual-tour-panorama-data.ts"
    },
    {
      "path": "packages/registry-react/blocks/virtual-tour-panorama/virtual-tour-panorama-types.ts",
      "content": "export interface RoomHotspot {\n  id: string\n  type: 'portal' | 'info'\n  x: number // Base % (0 - 100)\n  y: number // Pitch % (0 - 100)\n  title: string\n  subtitle: string\n  targetRoomId?: string\n  infoData?: {\n    category: string\n    specs: string[]\n    description: string\n    highlight: string\n    material: string\n  }\n}\n\nexport interface RoomMeasurement {\n  id: string\n  title: string\n  imperial: string\n  metric: string\n  x1: number\n  y1: number\n  x2: number\n  y2: number\n  labelX: number\n  labelY: number\n}\n\nexport interface RoomData {\n  id: string\n  name: string\n  badgeName: string\n  floor: string\n  area: string\n  exposure: string\n  ceilingHeight: string\n  description: string\n  skylineStyle: string\n  floorPlanCoords: { cx: number; cy: number; x: number; y: number; w: number; h: number; label: string }\n  hotspots: RoomHotspot[]\n  measurements: RoomMeasurement[]\n  features: { label: string; value: string }[]\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/virtual-tour-panorama/virtual-tour-panorama-types.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/separator.json"
  ],
  "description": "360-degree immersive room virtual tour panorama viewer with interactive viewpoint hotspots, laser measurement overlays, and architectural floor plan minimap.",
  "categories": [
    "real-estate",
    "app",
    "media"
  ]
}