{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "virtual-tour-panorama",
  "title": "Virtual Tour Panorama",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/virtual-tour-panorama/VirtualTourPanorama.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, ref } from 'vue'\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-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\nimport TourFloorPlanNavigator from './TourFloorPlanNavigator.vue'\nimport TourHotspotModal from './TourHotspotModal.vue'\nimport TourPanoramaScene from './TourPanoramaScene.vue'\nimport TourRoomDetails from './TourRoomDetails.vue'\nimport { defaultRooms } from './virtual-tour-panorama-data'\nimport type { RoomData, RoomHotspot } from './virtual-tour-panorama-types'\n\nconst props = withDefaults(\n  defineProps<{\n    rooms?: RoomData[]\n  }>(),\n  {\n    rooms: () => defaultRooms,\n  },\n)\n\nconst allRooms = computed(() => (props.rooms && props.rooms.length > 0 ? props.rooms : defaultRooms))\n\n// State variables\nconst currentRoomId = ref<string>('master-bedroom')\nconst yaw = ref<number>(0) // horizontal pan angle in degrees\nconst pitch = ref<number>(0) // vertical tilt in degrees (-20 to 20)\nconst zoom = ref<number>(1.0) // zoom level (0.9 to 1.6)\n\nconst isDragging = ref<boolean>(false)\nconst dragStartX = ref<number>(0)\nconst dragStartY = ref<number>(0)\nconst startYaw = ref<number>(0)\nconst startPitch = ref<number>(0)\n\nconst isAutoRotating = ref<boolean>(false)\nconst isMinimapOpen = ref<boolean>(true)\nconst isMeasurementMode = ref<boolean>(false)\nconst measurementUnit = ref<'imperial' | 'metric'>('imperial')\nconst isVrMode = ref<boolean>(false)\nconst isAudioActive = ref<boolean>(false)\nconst isFullscreen = ref<boolean>(false)\nconst copiedToast = ref<boolean>(false)\nconst isRoomTransitioning = ref<boolean>(false)\nconst activeInfoHotspot = ref<RoomHotspot | null>(null)\nconst hoveredHotspotId = ref<string | null>(null)\n\n// Current Room computed\nconst currentRoom = computed(() => {\n  return allRooms.value.find((r) => r.id === currentRoomId.value) || allRooms.value[0]\n})\n\n// Normalized Compass Heading (0 to 360)\nconst compassHeading = computed(() => {\n  const normalized = ((yaw.value % 360) + 360) % 360\n  return Math.round(normalized)\n})\n\nconst compassDirection = computed(() => {\n  const h = compassHeading.value\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})\n\n// Auto-rotation loop\nlet autoRotateRaf: number | null = null\n\nfunction updateAutoRotate() {\n  if (isAutoRotating.value && !isDragging.value) {\n    yaw.value = (yaw.value + 0.18) % 360\n  }\n  autoRotateRaf = requestAnimationFrame(updateAutoRotate)\n}\n\nonMounted(() => {\n  autoRotateRaf = requestAnimationFrame(updateAutoRotate)\n  window.addEventListener('keydown', handleKeydown)\n})\n\nonUnmounted(() => {\n  if (autoRotateRaf) cancelAnimationFrame(autoRotateRaf)\n  window.removeEventListener('keydown', handleKeydown)\n})\n\n// Room transition\nfunction switchRoom(roomId: string) {\n  if (roomId === currentRoomId.value) return\n  isRoomTransitioning.value = true\n  activeInfoHotspot.value = null\n\n  setTimeout(() => {\n    currentRoomId.value = roomId\n    yaw.value = 0\n    pitch.value = 0\n    setTimeout(() => {\n      isRoomTransitioning.value = false\n    }, 280)\n  }, 220)\n}\n\n// Mouse / Touch Interaction Handlers\nfunction onPointerDown(e: MouseEvent | TouchEvent) {\n  isDragging.value = 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  dragStartX.value = clientX\n  dragStartY.value = clientY\n  startYaw.value = yaw.value\n  startPitch.value = pitch.value\n}\n\nfunction onPointerMove(e: MouseEvent | TouchEvent) {\n  if (!isDragging.value) 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 - dragStartX.value\n  const deltaY = clientY - dragStartY.value\n\n  yaw.value = (startYaw.value - deltaX * 0.25 + 3600) % 360\n  pitch.value = Math.max(-22, Math.min(22, startPitch.value + deltaY * 0.18))\n}\n\nfunction onPointerUp() {\n  isDragging.value = false\n}\n\n// Pan step helpers\nfunction panStep(dx: number, dy: number) {\n  yaw.value = (yaw.value + dx + 3600) % 360\n  pitch.value = Math.max(-22, Math.min(22, pitch.value + dy))\n}\n\nfunction zoomStep(delta: number) {\n  zoom.value = Math.max(0.85, Math.min(1.5, zoom.value + delta))\n}\n\nfunction resetView() {\n  yaw.value = 0\n  pitch.value = 0\n  zoom.value = 1.0\n}\n\nfunction 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') isMinimapOpen.value = !isMinimapOpen.value\n  if (e.key === 'r' || e.key === 'R') isMeasurementMode.value = !isMeasurementMode.value\n}\n\nfunction copyTourShareLink() {\n  copiedToast.value = true\n  if (typeof navigator !== 'undefined' && navigator.clipboard) {\n    navigator.clipboard.writeText('https://uipkge.dev/tour/742-evergreen-terrace')\n  }\n  setTimeout(() => {\n    copiedToast.value = false\n  }, 3200)\n}\n\nfunction toggleFullscreen() {\n  isFullscreen.value = !isFullscreen.value\n}\n\nfunction getHotspotStyle(hotspot: RoomHotspot) {\n  const relativeX = Math.max(8, Math.min(92, (hotspot.x - (yaw.value / 360) * 100 + 150) % 100))\n  const relativeY = Math.max(8, Math.min(88, hotspot.y + pitch.value * 0.4))\n  return {\n    left: `${relativeX}%`,\n    top: `${relativeY}%`,\n  }\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</script>\n\n<template>\n  <div\n    data-slot=\"virtual-tour-panorama\"\n    :class=\"[\n      '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 class=\"bg-card rounded-xl border p-3.5 shadow-xs sm:px-5 sm:py-3\">\n      <div class=\"flex flex-wrap items-center justify-between gap-3\">\n        <!-- Property Identity & Room Badge -->\n        <div class=\"flex flex-wrap items-center gap-2.5 sm:gap-3\">\n          <div class=\"flex items-center gap-2\">\n            <div\n              class=\"bg-primary/10 text-primary border-primary/30 flex size-8 items-center justify-center rounded-lg border shadow-xs\"\n            >\n              <Compass class=\"size-4 animate-[spin_12s_linear_infinite]\" />\n            </div>\n            <div>\n              <div class=\"flex items-center gap-2\">\n                <h2 class=\"text-foreground text-sm font-bold tracking-tight sm:text-base\">742 Evergreen Terrace</h2>\n                <span class=\"text-muted-foreground hidden text-xs font-medium sm:inline\">360° Virtual Tour</span>\n              </div>\n              <p class=\"text-muted-foreground text-xs\">Penthouse Residence 14B · The Evergreen Collection</p>\n            </div>\n          </div>\n\n          <Separator orientation=\"vertical\" class=\"hidden h-6 sm:block\" />\n\n          <!-- Dynamic Active Room Badge -->\n          <Badge\n            variant=\"outline\"\n            class=\"border-primary/40 bg-primary/10 text-primary gap-1.5 px-2.5 py-1 text-xs font-semibold\"\n          >\n            <component :is=\"getRoomIcon(currentRoom.id)\" class=\"size-3.5\" />\n            <span>{{ currentRoom.badgeName }}</span>\n          </Badge>\n\n          <!-- Resolution & Live Feed Indicator -->\n          <div\n            class=\"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          >\n            <span class=\"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 class=\"flex items-center gap-2\">\n          <!-- Ambience Audio Toggle -->\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            :class=\"['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            @click=\"isAudioActive = !isAudioActive\"\n          >\n            <Volume2 v-if=\"isAudioActive\" class=\"text-info size-3.5\" />\n            <VolumeX v-else class=\"text-muted-foreground size-3.5\" />\n            <span class=\"hidden md:inline\">{{ isAudioActive ? 'Sound On' : 'Ambience' }}</span>\n          </Button>\n\n          <!-- Gyroscope / VR Mode Toggle -->\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            :class=\"['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            @click=\"isVrMode = !isVrMode\"\n          >\n            <Glasses class=\"size-3.5\" />\n            <span class=\"hidden sm:inline\">{{ isVrMode ? 'VR Active' : 'VR Mode' }}</span>\n          </Button>\n\n          <!-- Share Tour Link Button -->\n          <Button\n            variant=\"outline\"\n            size=\"sm\"\n            class=\"relative h-8 gap-1.5 px-2.5 text-xs\"\n            aria-label=\"Share 360 Tour\"\n            @click=\"copyTourShareLink\"\n          >\n            <Check v-if=\"copiedToast\" class=\"text-success size-3.5\" />\n            <Share2 v-else class=\"size-3.5\" />\n            <span class=\"hidden sm:inline\">{{ copiedToast ? 'Copied!' : 'Share' }}</span>\n          </Button>\n\n          <!-- Fullscreen Toggle -->\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            class=\"size-8\"\n            :aria-label=\"isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'\"\n            @click=\"toggleFullscreen\"\n          >\n            <Minimize2 v-if=\"isFullscreen\" class=\"size-3.5\" />\n            <Maximize2 v-else class=\"size-3.5\" />\n          </Button>\n        </div>\n      </div>\n    </header>\n\n    <!-- 360 Panorama Viewport Container -->\n    <div\n      class=\"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      @mousedown=\"onPointerDown\"\n      @mousemove=\"onPointerMove\"\n      @mouseup=\"onPointerUp\"\n      @mouseleave=\"onPointerUp\"\n      @touchstart=\"onPointerDown\"\n      @touchmove=\"onPointerMove\"\n      @touchend=\"onPointerUp\"\n    >\n      <TourPanoramaScene\n        :current-room=\"currentRoom\"\n        :yaw=\"yaw\"\n        :pitch=\"pitch\"\n        :zoom=\"zoom\"\n        :is-room-transitioning=\"isRoomTransitioning\"\n        :is-measurement-mode=\"isMeasurementMode\"\n        :measurement-unit=\"measurementUnit\"\n      />\n\n      <!-- Interactive Hotspot Pins Layer -->\n      <div class=\"pointer-events-auto absolute inset-0 size-full\">\n        <div\n          v-for=\"hotspot in currentRoom.hotspots\"\n          :key=\"hotspot.id\"\n          class=\"absolute -translate-x-1/2 -translate-y-1/2 transition-transform duration-100\"\n          :style=\"getHotspotStyle(hotspot)\"\n          @mouseenter=\"hoveredHotspotId = hotspot.id\"\n          @mouseleave=\"hoveredHotspotId = null\"\n        >\n          <!-- PORTAL HOTSPOT: Room Navigation -->\n          <div v-if=\"hotspot.type === 'portal'\" class=\"group relative flex flex-col items-center\">\n            <span class=\"bg-success absolute -inset-2.5 rounded-full opacity-60 duration-1000\" />\n            <span class=\"bg-success/40 absolute -inset-1 rounded-full opacity-80 blur-xs\" />\n\n            <button\n              type=\"button\"\n              class=\"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              @click.stop=\"hotspot.targetRoomId && switchRoom(hotspot.targetRoomId)\"\n            >\n              <Navigation class=\"size-4\" />\n            </button>\n\n            <!-- Floating Label Tooltip (Portal) -->\n            <div\n              :class=\"[\n                '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 class=\"text-success flex items-center gap-1 text-xs font-bold\">\n                <span>{{ hotspot.title }}</span>\n              </div>\n              <p class=\"text-muted-foreground mt-0.5 text-xs\">\n                {{ hotspot.subtitle }}\n              </p>\n              <span class=\"text-success mt-1 inline-flex items-center gap-1 font-mono text-xs font-semibold\">\n                Click to Transition <ArrowRight class=\"size-3\" />\n              </span>\n            </div>\n          </div>\n\n          <!-- INFO HOTSPOT: Feature Inspection -->\n          <div v-else-if=\"hotspot.type === 'info'\" class=\"group relative flex flex-col items-center\">\n            <span class=\"bg-info absolute -inset-2.5 rounded-full opacity-60 duration-1000\" />\n            <span class=\"bg-info/40 absolute -inset-1 rounded-full opacity-80 blur-xs\" />\n\n            <button\n              type=\"button\"\n              class=\"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              @click.stop=\"activeInfoHotspot = hotspot\"\n            >\n              <Info class=\"size-4\" />\n            </button>\n\n            <!-- Floating Label Tooltip (Info) -->\n            <div\n              :class=\"[\n                '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 class=\"text-info flex items-center gap-1 text-xs font-bold\">\n                <Info class=\"size-3\" />\n                <span>{{ hotspot.title }}</span>\n              </div>\n              <p class=\"text-muted-foreground mt-0.5 text-xs\">\n                {{ hotspot.subtitle }}\n              </p>\n              <span class=\"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        </div>\n      </div>\n\n      <!-- Top Overlay HUD: Compass, Pitch, FOV & Controls -->\n      <div\n        class=\"pointer-events-none absolute inset-x-3.5 top-3.5 z-20 flex flex-wrap items-start justify-between gap-2\"\n      >\n        <!-- Compass & Angle HUD Indicator -->\n        <div\n          class=\"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        >\n          <div class=\"flex items-center gap-1.5 font-mono text-xs font-bold text-zinc-100\">\n            <Compass\n              class=\"text-primary size-3.5 transition-transform duration-100\"\n              :style=\"{ transform: `rotate(${compassHeading}deg)` }\"\n            />\n            <span>{{ compassHeading }}° {{ compassDirection }}</span>\n          </div>\n          <Separator orientation=\"vertical\" class=\"h-3.5 bg-zinc-700\" />\n          <span class=\"text-muted-foreground font-mono text-xs\">FOV 85°</span>\n          <Separator orientation=\"vertical\" class=\"h-3.5 bg-zinc-700\" />\n          <span class=\"text-muted-foreground font-mono text-xs\"\n            >Pitch {{ pitch > 0 ? `+${Math.round(pitch)}°` : `${Math.round(pitch)}°` }}</span\n          >\n        </div>\n\n        <!-- Canvas Top Right Action Badges -->\n        <div class=\"pointer-events-auto flex items-center gap-2\">\n          <div\n            v-if=\"isVrMode\"\n            class=\"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          >\n            <Glasses class=\"size-3.5 animate-pulse\" />\n            <span>Gyroscope / VR Sensor Active</span>\n          </div>\n\n          <div\n            v-if=\"isMeasurementMode\"\n            class=\"border-destructive/50 flex items-center gap-1 rounded-lg border bg-zinc-900/90 p-1 backdrop-blur-md\"\n          >\n            <Button\n              size=\"sm\"\n              :variant=\"measurementUnit === 'imperial' ? 'destructive' : 'ghost'\"\n              class=\"h-6 px-2 text-xs\"\n              @click=\"measurementUnit = 'imperial'\"\n            >\n              Imperial (ft)\n            </Button>\n            <Button\n              size=\"sm\"\n              :variant=\"measurementUnit === 'metric' ? 'destructive' : 'ghost'\"\n              class=\"h-6 px-2 text-xs\"\n              @click=\"measurementUnit = 'metric'\"\n            >\n              Metric (m)\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      <!-- Floating 360 Pan & Zoom Controls (Center-Right overlay) -->\n      <div\n        class=\"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      >\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n          aria-label=\"Pan Up\"\n          @click=\"panStep(0, 8)\"\n        >\n          <ChevronUp class=\"size-4\" />\n        </Button>\n        <div class=\"flex items-center gap-1\">\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Pan Left\"\n            @click=\"panStep(-15, 0)\"\n          >\n            <ChevronLeft class=\"size-4\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Reset View\"\n            @click=\"resetView\"\n          >\n            <RotateCcw class=\"size-3.5\" />\n          </Button>\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n            aria-label=\"Pan Right\"\n            @click=\"panStep(15, 0)\"\n          >\n            <ChevronRight class=\"size-4\" />\n          </Button>\n        </div>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n          aria-label=\"Pan Down\"\n          @click=\"panStep(0, -8)\"\n        >\n          <ChevronDown class=\"size-4\" />\n        </Button>\n        <Separator class=\"my-0.5 bg-zinc-800\" />\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n          aria-label=\"Zoom In\"\n          @click=\"zoomStep(0.15)\"\n        >\n          <ZoomIn class=\"size-3.5\" />\n        </Button>\n        <Button\n          variant=\"ghost\"\n          size=\"icon\"\n          class=\"size-7 text-zinc-300 hover:bg-zinc-800 hover:text-white\"\n          aria-label=\"Zoom Out\"\n          @click=\"zoomStep(-0.15)\"\n        >\n          <ZoomOut class=\"size-3.5\" />\n        </Button>\n      </div>\n\n      <!-- 2D Floor Plan Minimap Overlay (Top-Left corner dock) -->\n      <TourFloorPlanNavigator\n        v-if=\"isMinimapOpen\"\n        :rooms=\"allRooms\"\n        :current-room-id=\"currentRoomId\"\n        :current-room=\"currentRoom\"\n        :yaw=\"yaw\"\n        @select-room=\"switchRoom\"\n        @close=\"isMinimapOpen = false\"\n      />\n\n      <!-- Feature Inspection Floating Detail Card Modal -->\n      <TourHotspotModal v-if=\"activeInfoHotspot\" :hotspot=\"activeInfoHotspot\" @close=\"activeInfoHotspot = null\" />\n\n      <!-- Bottom Floating Navigation Dock (Room Switcher & Tool Controls) -->\n      <div class=\"pointer-events-auto absolute inset-x-3.5 bottom-3.5 z-30 flex items-center justify-center\">\n        <div\n          class=\"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        >\n          <!-- Auto-Rotate 360 Toggle -->\n          <Button\n            :variant=\"isAutoRotating ? 'default' : 'secondary'\"\n            size=\"sm\"\n            class=\"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            @click=\"isAutoRotating = !isAutoRotating\"\n          >\n            <Pause v-if=\"isAutoRotating\" class=\"size-3.5\" />\n            <Play v-else class=\"size-3.5\" />\n            <span class=\"hidden sm:inline\">{{ isAutoRotating ? 'Pause Tour' : 'Auto Tour' }}</span>\n          </Button>\n\n          <Separator orientation=\"vertical\" class=\"hidden h-5 bg-zinc-800 sm:block\" />\n\n          <!-- Room Switcher Thumbnails Carousel -->\n          <div class=\"flex items-center gap-1.5 overflow-x-auto py-0.5\">\n            <button\n              v-for=\"r in allRooms\"\n              :key=\"r.id\"\n              type=\"button\"\n              :class=\"[\n                '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              @click=\"switchRoom(r.id)\"\n            >\n              <component :is=\"getRoomIcon(r.id)\" class=\"size-3.5\" />\n              <span class=\"whitespace-nowrap\">{{ r.name }}</span>\n            </button>\n          </div>\n\n          <Separator orientation=\"vertical\" class=\"hidden h-5 bg-zinc-800 sm:block\" />\n\n          <!-- Minimap Toggle Button -->\n          <Button\n            :variant=\"isMinimapOpen ? 'default' : 'secondary'\"\n            size=\"sm\"\n            class=\"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            @click=\"isMinimapOpen = !isMinimapOpen\"\n          >\n            <MapIcon class=\"size-3.5\" />\n            <span class=\"hidden md:inline\">Minimap</span>\n          </Button>\n\n          <!-- Laser Measurement Toggle Button -->\n          <Button\n            :variant=\"isMeasurementMode ? 'destructive' : 'secondary'\"\n            size=\"sm\"\n            class=\"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            @click=\"isMeasurementMode = !isMeasurementMode\"\n          >\n            <Ruler class=\"size-3.5\" />\n            <span class=\"hidden md:inline\">Measure</span>\n          </Button>\n        </div>\n      </div>\n    </div>\n\n    <!-- Architectural Deep Dive Specs & Floor Schedule Cards -->\n    <TourRoomDetails :current-room=\"currentRoom\" />\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/virtual-tour-panorama/VirtualTourPanorama.vue"
    },
    {
      "path": "packages/registry-vue/blocks/virtual-tour-panorama/TourFloorPlanNavigator.vue",
      "content": "<script setup lang=\"ts\">\nimport { Map as MapIcon, X } from 'lucide-vue-next'\nimport { Button } from '@/components/ui/button'\nimport type { RoomData } from './virtual-tour-panorama-types'\n\ndefineProps<{\n  rooms: RoomData[]\n  currentRoomId: string\n  currentRoom: RoomData\n  yaw: number\n}>()\n\ndefineEmits<{\n  'select-room': [roomId: string]\n  close: []\n}>()\n</script>\n\n<template>\n  <!-- 2D Floor Plan Minimap Overlay (Top-Left corner dock) -->\n  <div\n    class=\"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  >\n    <div class=\"flex items-center justify-between border-b border-zinc-800 bg-zinc-900/90 px-3 py-1.5\">\n      <div class=\"flex items-center gap-1.5\">\n        <MapIcon class=\"text-primary size-3.5\" />\n        <span class=\"text-xs font-bold text-zinc-200\">Floor Plan Minimap</span>\n      </div>\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        class=\"text-muted-foreground size-5 hover:text-white\"\n        aria-label=\"Close minimap\"\n        @click=\"$emit('close')\"\n      >\n        <X class=\"size-3\" />\n      </Button>\n    </div>\n\n    <div class=\"relative p-2.5\">\n      <!-- 2D CAD Blueprint Floor Plan SVG -->\n      <svg class=\"w-full\" viewBox=\"0 0 240 160\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <defs>\n          <!-- Radar FOV Gradient -->\n          <radialGradient id=\"fovGradient\" cx=\"0\" cy=\"0\" r=\"100%\" gradientUnits=\"userSpaceOnUse\">\n            <stop offset=\"0%\" stop-color=\"#10b981\" stop-opacity=\"0.85\" />\n            <stop offset=\"60%\" stop-color=\"#10b981\" stop-opacity=\"0.35\" />\n            <stop offset=\"100%\" stop-color=\"#10b981\" stop-opacity=\"0.0\" />\n          </radialGradient>\n        </defs>\n\n        <!-- Floor Plan Room Outlines -->\n        <g v-for=\"r in rooms\" :key=\"'map-r-' + r.id\" class=\"cursor-pointer\" @click=\"$emit('select-room', 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            :class=\"[\n              '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        <!-- 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(#fovGradient)\" />\n          <line x1=\"0\" y1=\"0\" x2=\"-22\" y2=\"-44\" stroke=\"var(--success)\" stroke-width=\"1\" stroke-dasharray=\"2 2\" />\n          <line x1=\"0\" y1=\"0\" x2=\"22\" y2=\"-44\" stroke=\"var(--success)\" stroke-width=\"1\" stroke-dasharray=\"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          stroke-width=\"1.5\"\n        />\n\n        <!-- Room Labels (painted last so the FOV cone and viewpoint marker never cover them) -->\n        <text\n          v-for=\"r in rooms\"\n          :key=\"'map-l-' + r.id\"\n          :x=\"r.floorPlanCoords.cx\"\n          :y=\"r.floorPlanCoords.cy + 4\"\n          text-anchor=\"middle\"\n          :class=\"[\n            'pointer-events-none text-xs font-semibold select-none',\n            currentRoomId === r.id ? 'fill-success font-bold' : 'fill-muted-foreground',\n          ]\"\n          style=\"font-size: 9.5px\"\n        >\n          {{ r.floorPlanCoords.label }}\n        </text>\n      </svg>\n\n      <div class=\"text-muted-foreground mt-1 flex items-center justify-between text-xs\">\n        <span class=\"font-mono\">North ↑</span>\n        <span>Click room zone to jump</span>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/virtual-tour-panorama/TourFloorPlanNavigator.vue"
    },
    {
      "path": "packages/registry-vue/blocks/virtual-tour-panorama/TourHotspotModal.vue",
      "content": "<script setup lang=\"ts\">\nimport { CheckCircle2, X } from 'lucide-vue-next'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport type { RoomHotspot } from './virtual-tour-panorama-types'\n\ndefineProps<{\n  hotspot: RoomHotspot\n}>()\n\ndefineEmits<{\n  close: []\n}>()\n</script>\n\n<template>\n  <div\n    v-if=\"hotspot && hotspot.infoData\"\n    class=\"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  >\n    <div class=\"flex items-start justify-between gap-3 border-b border-zinc-800 pb-3\">\n      <div class=\"space-y-1\">\n        <div class=\"flex items-center gap-2\">\n          <Badge variant=\"outline\" class=\"border-info/50 bg-info/10 text-info text-xs font-semibold\">\n            {{ hotspot.infoData.category }}\n          </Badge>\n          <span class=\"text-muted-foreground font-mono text-xs\">{{ hotspot.infoData.highlight }}</span>\n        </div>\n        <h3 class=\"text-sm font-bold text-white sm:text-base\">\n          {{ hotspot.title }}\n        </h3>\n      </div>\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        class=\"text-muted-foreground size-7 hover:text-white\"\n        aria-label=\"Close feature modal\"\n        @click=\"$emit('close')\"\n      >\n        <X class=\"size-4\" />\n      </Button>\n    </div>\n\n    <div class=\"space-y-3 pt-3 text-xs\">\n      <p class=\"leading-relaxed text-zinc-300\">\n        {{ hotspot.infoData.description }}\n      </p>\n\n      <div class=\"space-y-1.5 rounded-lg border border-zinc-800 bg-zinc-900/60 p-2.5\">\n        <span class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n          Architectural Specifications\n        </span>\n        <ul class=\"grid grid-cols-1 gap-1 text-zinc-200 sm:grid-cols-2\">\n          <li v-for=\"(spec, idx) in hotspot.infoData.specs\" :key=\"idx\" class=\"flex items-center gap-1.5\">\n            <CheckCircle2 class=\"text-info size-3 shrink-0\" />\n            <span class=\"truncate\">{{ spec }}</span>\n          </li>\n        </ul>\n      </div>\n\n      <div class=\"text-muted-foreground flex items-center justify-between border-t border-zinc-800 pt-2 text-xs\">\n        <span>\n          Finishes: <strong class=\"text-zinc-200\">{{ hotspot.infoData.material }}</strong>\n        </span>\n        <Button size=\"sm\" class=\"h-7 text-xs\" @click=\"$emit('close')\"> Done </Button>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/virtual-tour-panorama/TourHotspotModal.vue"
    },
    {
      "path": "packages/registry-vue/blocks/virtual-tour-panorama/TourPanoramaScene.vue",
      "content": "<script setup lang=\"ts\">\nimport { Ruler } from 'lucide-vue-next'\nimport type { RoomData } from './virtual-tour-panorama-types'\n\ndefineProps<{\n  currentRoom: RoomData\n  yaw: number\n  pitch: number\n  zoom: number\n  isRoomTransitioning: boolean\n  isMeasurementMode: boolean\n  measurementUnit: 'imperial' | 'metric'\n}>()\n</script>\n\n<template>\n  <!-- Simulated 360 High-Resolution Architectural Panorama Art Canvas -->\n  <div\n    :class=\"[\n      '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 :class=\"['absolute inset-0 bg-gradient-to-b', currentRoom.skylineStyle]\" />\n\n    <!-- Architectural Wireframe / Perspective Room Illustration -->\n    <svg\n      class=\"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        <!-- Window Horizon Glow -->\n        <radialGradient id=\"sunGlow\" cx=\"50%\" cy=\"40%\" r=\"50%\">\n          <stop offset=\"0%\" stop-color=\"#fbbf24\" stop-opacity=\"0.35\" />\n          <stop offset=\"60%\" stop-color=\"#f43f5e\" stop-opacity=\"0.15\" />\n          <stop offset=\"100%\" stop-color=\"#000000\" stop-opacity=\"0\" />\n        </radialGradient>\n        <!-- Linear Floor Reflection -->\n        <linearGradient id=\"floorGrad\" x1=\"50%\" y1=\"0%\" x2=\"50%\" y2=\"100%\">\n          <stop offset=\"0%\" stop-color=\"#0f172a\" stop-opacity=\"0.9\" />\n          <stop offset=\"40%\" stop-color=\"#1e293b\" stop-opacity=\"0.95\" />\n          <stop offset=\"100%\" stop-color=\"#020617\" stop-opacity=\"1\" />\n        </linearGradient>\n        <!-- Ceiling Gradient -->\n        <linearGradient id=\"ceilingGrad\" x1=\"50%\" y1=\"0%\" x2=\"50%\" y2=\"100%\">\n          <stop offset=\"0%\" stop-color=\"#020617\" />\n          <stop offset=\"100%\" stop-color=\"#0f172a\" stop-opacity=\"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(#sunGlow)\" />\n\n        <!-- Skyline Cityscape Silhouettes in the distance -->\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          fill-opacity=\"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          fill-opacity=\"0.65\"\n        />\n\n        <!-- Architectural Window Mullions (Floor to ceiling) -->\n        <line x1=\"0\" y1=\"90\" x2=\"0\" y2=\"400\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"250\" y1=\"90\" x2=\"250\" y2=\"400\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"500\" y1=\"90\" x2=\"500\" y2=\"400\" stroke=\"#475569\" stroke-width=\"8\" />\n        <line x1=\"750\" y1=\"90\" x2=\"750\" y2=\"400\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"1000\" y1=\"90\" x2=\"1000\" y2=\"400\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"1250\" y1=\"90\" x2=\"1250\" y2=\"400\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"-300\" y1=\"90\" x2=\"1300\" y2=\"90\" stroke=\"#334155\" stroke-width=\"6\" />\n        <line x1=\"-300\" y1=\"400\" x2=\"1300\" y2=\"400\" stroke=\"#334155\" stroke-width=\"8\" />\n\n        <!-- Room-Specific Detailed Focal Art Piece -->\n        <g v-if=\"currentRoom.id === 'master-bedroom'\">\n          <!-- King Upholstered Bed & Acoustic Slat Headboard -->\n          <rect x=\"360\" y=\"310\" width=\"280\" height=\"90\" rx=\"4\" fill=\"#1e293b\" stroke=\"#475569\" stroke-width=\"2\" />\n          <rect x=\"380\" y=\"240\" width=\"240\" height=\"70\" rx=\"6\" fill=\"#334155\" stroke=\"#64748b\" stroke-width=\"2\" />\n          <!-- Slat vertical lines -->\n          <line x1=\"400\" y1=\"240\" x2=\"400\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"420\" y1=\"240\" x2=\"420\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"440\" y1=\"240\" x2=\"440\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"460\" y1=\"240\" x2=\"460\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"480\" y1=\"240\" x2=\"480\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"500\" y1=\"240\" x2=\"500\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"520\" y1=\"240\" x2=\"520\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"540\" y1=\"240\" x2=\"540\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"560\" y1=\"240\" x2=\"560\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"580\" y1=\"240\" x2=\"580\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <line x1=\"600\" y1=\"240\" x2=\"600\" y2=\"310\" stroke=\"#475569\" stroke-width=\"1.5\" />\n          <!-- Pillows & Bed Spread -->\n          <ellipse cx=\"430\" cy=\"330\" rx=\"35\" ry=\"15\" fill=\"#e2e8f0\" fill-opacity=\"0.8\" />\n          <ellipse cx=\"570\" cy=\"330\" rx=\"35\" ry=\"15\" fill=\"#e2e8f0\" fill-opacity=\"0.8\" />\n          <!-- Bedside floating pedestals -->\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\" fill-opacity=\"0.6\" />\n          <circle cx=\"675\" cy=\"315\" r=\"8\" fill=\"#f59e0b\" fill-opacity=\"0.6\" />\n        </g>\n\n        <g v-else-if=\"currentRoom.id === 'living-room'\">\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            stroke-width=\"2\"\n          />\n          <rect x=\"420\" y=\"400\" width=\"160\" height=\"40\" rx=\"8\" fill=\"#475569\" stroke=\"#94a3b8\" stroke-width=\"1.5\" />\n          <!-- Linear bioethanol fireplace on the wall -->\n          <rect x=\"400\" y=\"270\" width=\"200\" height=\"35\" rx=\"4\" fill=\"#09090b\" stroke=\"#f59e0b\" stroke-width=\"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            stroke-width=\"3\"\n            fill=\"none\"\n          />\n        </g>\n\n        <g v-else-if=\"currentRoom.id === 'gourmet-kitchen'\">\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            stroke-width=\"2\"\n            stroke-dasharray=\"400 4\"\n          />\n          <rect x=\"360\" y=\"325\" width=\"280\" height=\"15\" rx=\"3\" fill=\"#1e293b\" stroke=\"#64748b\" />\n          <!-- Barstools -->\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        <g v-else-if=\"currentRoom.id === 'rooftop-terrace'\">\n          <!-- Infinity Plunge Pool & Lounges -->\n          <rect x=\"520\" y=\"340\" width=\"340\" height=\"90\" rx=\"8\" fill=\"#0369a1\" stroke=\"#38bdf8\" stroke-width=\"3\" />\n          <path\n            d=\"M540 370 Q600 360 660 370 Q720 380 780 370 Q820 360 840 370\"\n            stroke=\"#7dd3fc\"\n            stroke-width=\"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            stroke-width=\"2\"\n            fill=\"none\"\n          />\n          <!-- Lounge Chairs -->\n          <rect x=\"220\" y=\"360\" width=\"100\" height=\"40\" rx=\"4\" fill=\"#78350f\" stroke=\"#b45309\" stroke-width=\"1.5\" />\n          <rect x=\"350\" y=\"360\" width=\"100\" height=\"40\" rx=\"4\" fill=\"#78350f\" stroke=\"#b45309\" stroke-width=\"1.5\" />\n        </g>\n\n        <g v-else-if=\"currentRoom.id === 'spa-bath'\">\n          <!-- Freestanding Oval Soaking Tub & Dual Mirror Wall -->\n          <ellipse cx=\"500\" cy=\"380\" rx=\"140\" ry=\"45\" fill=\"#f8fafc\" stroke=\"#cbd5e1\" stroke-width=\"3\" />\n          <ellipse cx=\"500\" cy=\"380\" rx=\"115\" ry=\"32\" fill=\"#0284c7\" fill-opacity=\"0.4\" />\n          <!-- Backlit Mirrors -->\n          <circle cx=\"340\" cy=\"240\" r=\"45\" fill=\"#1e293b\" stroke=\"#f59e0b\" stroke-width=\"2\" />\n          <circle cx=\"660\" cy=\"240\" r=\"45\" fill=\"#1e293b\" stroke=\"#f59e0b\" stroke-width=\"2\" />\n        </g>\n      </g>\n\n      <!-- Perspective Floor Grid (Isometric luxury perspective) -->\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        stroke-opacity=\"0.25\"\n        stroke-width=\"1.5\"\n      />\n      <line x1=\"-200\" y1=\"440\" x2=\"1200\" y2=\"440\" stroke=\"#334155\" stroke-opacity=\"0.25\" stroke-width=\"1.5\" />\n      <line x1=\"-200\" y1=\"490\" x2=\"1200\" y2=\"490\" stroke=\"#334155\" stroke-opacity=\"0.25\" stroke-width=\"1.5\" />\n      <line x1=\"-200\" y1=\"545\" x2=\"1200\" y2=\"545\" stroke=\"#334155\" stroke-opacity=\"0.25\" stroke-width=\"1.5\" />\n\n      <!-- Ceiling Cove Recessed Glow line -->\n      <line x1=\"0\" y1=\"40\" x2=\"1000\" y2=\"40\" stroke=\"#fbbf24\" stroke-opacity=\"0.3\" stroke-width=\"2\" />\n    </svg>\n\n    <!-- Dynamic Laser Measurement Overlay (When enabled) -->\n    <svg\n      v-if=\"isMeasurementMode\"\n      class=\"pointer-events-none absolute inset-0 size-full transition-opacity duration-300\"\n      preserveAspectRatio=\"none\"\n      viewBox=\"0 0 100 100\"\n    >\n      <g v-for=\"m in currentRoom.measurements\" :key=\"m.id\">\n        <!-- Calibrated Laser line -->\n        <line\n          :x1=\"m.x1\"\n          :y1=\"m.y1\"\n          :x2=\"m.x2\"\n          :y2=\"m.y2\"\n          stroke=\"#f43f5e\"\n          stroke-width=\"0.75\"\n          stroke-dasharray=\"2 1.5\"\n        />\n        <!-- Endpoint anchors -->\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    </svg>\n\n    <!-- Measurement Pill Badges (HTML Overlay) -->\n    <div v-if=\"isMeasurementMode\" class=\"pointer-events-none absolute inset-0 size-full\">\n      <div\n        v-for=\"m in currentRoom.measurements\"\n        :key=\"'badge-' + m.id\"\n        class=\"absolute -translate-x-1/2 -translate-y-1/2\"\n        :style=\"{ left: `${m.labelX}%`, top: `${m.labelY}%` }\"\n      >\n        <div\n          class=\"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        >\n          <Ruler class=\"size-3\" />\n          <span>{{ m.title }}:</span>\n          <span class=\"text-white\">\n            {{ measurementUnit === 'imperial' ? m.imperial : m.metric }}\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/virtual-tour-panorama/TourPanoramaScene.vue"
    },
    {
      "path": "packages/registry-vue/blocks/virtual-tour-panorama/TourRoomDetails.vue",
      "content": "<script setup lang=\"ts\">\nimport { ArrowRight, Bath, Bed, Calendar, Home, ShieldCheck, Sofa, Sun, Utensils } from 'lucide-vue-next'\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\ndefineProps<{\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</script>\n\n<template>\n  <div class=\"grid grid-cols-1 gap-4 lg:grid-cols-12\">\n    <!-- Active Room Overview Card -->\n    <Card class=\"shadow-xs lg:col-span-8\">\n      <CardHeader class=\"pb-3\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div>\n            <CardTitle class=\"flex items-center gap-2 text-base font-bold sm:text-lg\">\n              <component :is=\"getRoomIcon(currentRoom.id)\" class=\"text-primary size-5\" />\n              <span>{{ currentRoom.badgeName }}</span>\n            </CardTitle>\n            <CardDescription class=\"text-xs\">\n              {{ currentRoom.exposure }} · {{ currentRoom.floor }} · Verified Architectural Plan\n            </CardDescription>\n          </div>\n          <Badge variant=\"outline\" class=\"font-mono text-xs\">\n            {{ currentRoom.area }}\n          </Badge>\n        </div>\n      </CardHeader>\n      <CardContent class=\"space-y-4\">\n        <p class=\"text-muted-foreground text-xs leading-relaxed sm:text-sm\">\n          {{ currentRoom.description }}\n        </p>\n\n        <!-- Room Architectural Metrics Grid -->\n        <div class=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n          <div class=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n            <span class=\"text-muted-foreground block\">Ceiling Height</span>\n            <span class=\"text-foreground font-mono font-bold\">{{ currentRoom.ceilingHeight }}</span>\n          </div>\n          <div class=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n            <span class=\"text-muted-foreground block\">Floor Area</span>\n            <span class=\"text-foreground font-mono font-bold\">{{ currentRoom.area.split(' ')[0] }} sq ft</span>\n          </div>\n          <div class=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n            <span class=\"text-muted-foreground block\">Solar Aspect</span>\n            <span class=\"text-foreground font-semibold\">{{ currentRoom.exposure.split(' ')[0] }}</span>\n          </div>\n          <div class=\"bg-muted/40 space-y-1 rounded-lg border p-2.5 text-xs\">\n            <span class=\"text-muted-foreground block\">Tour Hotspots</span>\n            <span class=\"text-foreground font-mono font-bold\">{{ currentRoom.hotspots.length }} Viewpoints</span>\n          </div>\n        </div>\n\n        <!-- Feature Bullet List -->\n        <div class=\"space-y-2 border-t pt-3\">\n          <h4 class=\"text-muted-foreground text-xs font-bold tracking-wider uppercase\">\n            Finishes & Smart Building Integration\n          </h4>\n          <div class=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n            <div\n              v-for=\"(f, i) in currentRoom.features\"\n              :key=\"i\"\n              class=\"bg-muted/20 flex items-start gap-2 rounded-md border p-2 text-xs\"\n            >\n              <ShieldCheck class=\"text-success mt-0.5 size-3.5 shrink-0\" />\n              <div>\n                <span class=\"text-muted-foreground font-medium\">{{ f.label }}: </span>\n                <span class=\"text-foreground font-semibold\">{{ f.value }}</span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n\n    <!-- Penthouse Residence Quick Facts & Viewing Inquire Card -->\n    <Card class=\"shadow-xs lg:col-span-4\">\n      <CardHeader class=\"pb-3\">\n        <CardTitle class=\"text-base font-bold\">Residence 14B Specs</CardTitle>\n        <CardDescription class=\"text-xs\"> The Evergreen Penthouse Collection </CardDescription>\n      </CardHeader>\n      <CardContent class=\"space-y-3.5 text-xs\">\n        <div class=\"space-y-2\">\n          <div class=\"flex items-center justify-between border-b pb-1.5\">\n            <span class=\"text-muted-foreground\">Total Living Area</span>\n            <span class=\"font-mono font-bold\">2,270 sq ft (210.8 m²)</span>\n          </div>\n          <div class=\"flex items-center justify-between border-b pb-1.5\">\n            <span class=\"text-muted-foreground\">Bedrooms / Baths</span>\n            <span class=\"font-semibold\">3 Bed · 3.5 Bath</span>\n          </div>\n          <div class=\"flex items-center justify-between border-b pb-1.5\">\n            <span class=\"text-muted-foreground\">Private Outdoor Sky Deck</span>\n            <span class=\"font-mono font-bold\">540 sq ft (Level 15)</span>\n          </div>\n          <div class=\"flex items-center justify-between border-b pb-1.5\">\n            <span class=\"text-muted-foreground\">Dedicated Valet EV Parking</span>\n            <span class=\"font-semibold\">2 Stalls (Level P1)</span>\n          </div>\n          <div class=\"flex items-center justify-between\">\n            <span class=\"text-muted-foreground\">Monthly HOA & Concierge</span>\n            <span class=\"font-mono font-bold\">$1,840 / mo</span>\n          </div>\n        </div>\n\n        <div class=\"bg-primary/5 border-primary/20 space-y-1 rounded-lg border p-3\">\n          <div class=\"text-primary flex items-center gap-1.5 font-semibold\">\n            <Calendar class=\"size-3.5\" />\n            <span>Private VIP Walkthroughs</span>\n          </div>\n          <p class=\"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 class=\"pt-0\">\n        <Button class=\"w-full gap-2 text-xs font-semibold shadow-xs\">\n          <span>Schedule In-Person Penthouse Tour</span>\n          <ArrowRight class=\"size-3.5\" />\n        </Button>\n      </CardFooter>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/virtual-tour-panorama/TourRoomDetails.vue"
    },
    {
      "path": "packages/registry-vue/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": "~/app/components/blocks/virtual-tour-panorama/virtual-tour-panorama-data.ts"
    },
    {
      "path": "packages/registry-vue/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": "~/app/components/blocks/virtual-tour-panorama/virtual-tour-panorama-types.ts"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/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"
  ]
}