{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "property-listing-card",
  "title": "Property Listing Card",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/property-listing-card/PropertyListingCard.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport {\n  BadgeCheck,\n  Bath,\n  BedDouble,\n  Box,\n  Calculator,\n  Calendar,\n  Camera,\n  Check,\n  ChevronLeft,\n  ChevronRight,\n  Compass,\n  Heart,\n  Mail,\n  MapPin,\n  Maximize2,\n  Share2,\n} from 'lucide-vue-next'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\nimport { cn } from '@/lib/utils'\n\ninterface PropertyItem {\n  id: string\n  title: string\n  propertyType: string\n  category: 'all' | 'single-family' | 'waterfront' | 'penthouse'\n  price: number\n  mortgageEst: string\n  address: {\n    street: string\n    cityStateZip: string\n    neighborhood: string\n  }\n  specs: {\n    beds: number\n    baths: number\n    sqft: number\n    pricePerSqft: number\n    yearBuilt: number\n    lotSize: string\n  }\n  badge: {\n    label: string\n    variant: 'default' | 'secondary' | 'outline' | 'destructive'\n  }\n  hasVirtualTour: boolean\n  openHouse: string\n  totalPhotos: number\n  images: string[]\n  tags: string[]\n  agent: {\n    name: string\n    brokerage: string\n    avatar: string\n    initials: string\n    badge: string\n  }\n}\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\nconst PROPERTIES: PropertyItem[] = [\n  {\n    id: 'prop-pasadena',\n    title: 'Mid-Century Modern Architectural Sanctuary',\n    propertyType: 'Single Family Residence',\n    category: 'single-family',\n    price: 1245000,\n    mortgageEst: 'Est. $6,420 / mo',\n    address: {\n      street: '742 Evergreen Terrace',\n      cityStateZip: 'Pasadena, CA 91101',\n      neighborhood: 'San Rafael Hills',\n    },\n    specs: {\n      beds: 4,\n      baths: 3.5,\n      sqft: 2850,\n      pricePerSqft: 437,\n      yearBuilt: 1962,\n      lotSize: '0.34 Acres',\n    },\n    badge: {\n      label: 'New Listing',\n      variant: 'default',\n    },\n    hasVirtualTour: true,\n    openHouse: 'Open Sun 1:00 – 4:00 PM',\n    totalPhotos: 18,\n    images: [\n      'https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?w=1200&auto=format&fit=crop&q=80',\n    ],\n    tags: ['Swimming Pool', 'Solar Panels', '2-Car Garage', 'Modern Kitchen', 'Mountain View'],\n    agent: {\n      name: 'Elena Rostova',\n      brokerage: \"Sotheby's International Realty\",\n      avatar: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=200&auto=format&fit=crop&q=80',\n      initials: 'ER',\n      badge: 'Top 1% Producer',\n    },\n  },\n  {\n    id: 'prop-carmel',\n    title: 'Coastal Craftsman Villa with Ocean Vista',\n    propertyType: 'Luxury Waterfront Estate',\n    category: 'waterfront',\n    price: 2890000,\n    mortgageEst: 'Est. $14,850 / mo',\n    address: {\n      street: '1840 Ocean View Avenue',\n      cityStateZip: 'Carmel-by-the-Sea, CA 93921',\n      neighborhood: 'Point Lobos Highlands',\n    },\n    specs: {\n      beds: 5,\n      baths: 4.5,\n      sqft: 4120,\n      pricePerSqft: 701,\n      yearBuilt: 2021,\n      lotSize: '0.52 Acres',\n    },\n    badge: {\n      label: 'Price Drop $100k',\n      variant: 'secondary',\n    },\n    hasVirtualTour: true,\n    openHouse: 'Private Viewings Only',\n    totalPhotos: 24,\n    images: [\n      'https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600585152220-90363fe7e115?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1584622650111-993a426fbf0a?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1600573472592-401b489a3cdc?w=1200&auto=format&fit=crop&q=80',\n    ],\n    tags: ['Ocean Frontage', 'Wine Cellar', 'Infinity Pool', 'Smart Automation', 'Chef Kitchen'],\n    agent: {\n      name: 'Marcus Vance',\n      brokerage: 'Compass Premier Collection',\n      avatar: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=200&auto=format&fit=crop&q=80',\n      initials: 'MV',\n      badge: 'Luxury Director',\n    },\n  },\n  {\n    id: 'prop-downtown',\n    title: 'Skyline Glass Penthouse & Private Terrace',\n    propertyType: 'High-Rise Penthouse Condo',\n    category: 'penthouse',\n    price: 895000,\n    mortgageEst: 'Est. $4,680 / mo',\n    address: {\n      street: '450 S Grand Avenue, Unit 38B',\n      cityStateZip: 'Los Angeles, CA 90071',\n      neighborhood: 'Bunker Hill Downtown',\n    },\n    specs: {\n      beds: 2,\n      baths: 2.0,\n      sqft: 1640,\n      pricePerSqft: 545,\n      yearBuilt: 2019,\n      lotSize: 'HOA $620 / mo',\n    },\n    badge: {\n      label: 'Just Listed',\n      variant: 'default',\n    },\n    hasVirtualTour: true,\n    openHouse: 'Open Sat 2:00 – 5:00 PM',\n    totalPhotos: 14,\n    images: [\n      'https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1556911220-e15b29be8c8f?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1513694203232-719a280e022f?w=1200&auto=format&fit=crop&q=80',\n      'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=1200&auto=format&fit=crop&q=80',\n    ],\n    tags: ['Private Terrace', '24/7 Concierge', 'Floor-to-Ceiling Windows', 'EV Parking', 'Fitness Center'],\n    agent: {\n      name: 'Aria Chen',\n      brokerage: 'Coldwell Banker Global Luxury',\n      avatar: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=200&auto=format&fit=crop&q=80',\n      initials: 'AC',\n      badge: 'Downtown Specialist',\n    },\n  },\n]\n\nconst activePhotoIndex = ref<Record<string, number>>({\n  'prop-pasadena': 0,\n  'prop-carmel': 0,\n  'prop-downtown': 0,\n})\n\nconst wishlistedIds = ref<Set<string>>(new Set(['prop-pasadena']))\nconst scheduledTourIds = ref<Set<string>>(new Set())\nconst contactedAgentIds = ref<Set<string>>(new Set())\nconst copiedLinkId = ref<string | null>(null)\nconst activeCategoryFilter = ref<string>('all')\nconst activeTourModalId = ref<string | null>(null)\n\nfunction getPhotoIndex(id: string): number {\n  return activePhotoIndex.value[id] ?? 0\n}\n\nfunction prevPhoto(id: string, total: number) {\n  const current = getPhotoIndex(id)\n  activePhotoIndex.value = {\n    ...activePhotoIndex.value,\n    [id]: (current - 1 + total) % total,\n  }\n}\n\nfunction nextPhoto(id: string, total: number) {\n  const current = getPhotoIndex(id)\n  activePhotoIndex.value = {\n    ...activePhotoIndex.value,\n    [id]: (current + 1) % total,\n  }\n}\n\nfunction setPhoto(id: string, index: number) {\n  activePhotoIndex.value = {\n    ...activePhotoIndex.value,\n    [id]: index,\n  }\n}\n\nfunction toggleWishlist(id: string) {\n  const next = new Set(wishlistedIds.value)\n  if (next.has(id)) {\n    next.delete(id)\n  } else {\n    next.add(id)\n  }\n  wishlistedIds.value = next\n}\n\nfunction isWishlisted(id: string): boolean {\n  return wishlistedIds.value.has(id)\n}\n\nfunction toggleScheduleTour(id: string) {\n  const next = new Set(scheduledTourIds.value)\n  if (next.has(id)) {\n    next.delete(id)\n  } else {\n    next.add(id)\n  }\n  scheduledTourIds.value = next\n}\n\nfunction isTourScheduled(id: string): boolean {\n  return scheduledTourIds.value.has(id)\n}\n\nfunction toggleContactAgent(id: string) {\n  const next = new Set(contactedAgentIds.value)\n  if (next.has(id)) {\n    next.delete(id)\n  } else {\n    next.add(id)\n  }\n  contactedAgentIds.value = next\n}\n\nfunction isAgentContacted(id: string): boolean {\n  return contactedAgentIds.value.has(id)\n}\n\nfunction shareListing(id: string, street: string) {\n  const url = `https://uipkge.dev/listings/${id}`\n  if (navigator?.clipboard?.writeText) {\n    navigator.clipboard.writeText(url)\n  }\n  copiedLinkId.value = id\n  setTimeout(() => {\n    if (copiedLinkId.value === id) {\n      copiedLinkId.value = null\n    }\n  }, 2000)\n}\n\nfunction toggleVirtualTour(id: string) {\n  if (activeTourModalId.value === id) {\n    activeTourModalId.value = null\n  } else {\n    activeTourModalId.value = id\n  }\n}\n\nfunction formatPrice(amount: number): string {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD',\n    maximumFractionDigits: 0,\n  }).format(amount)\n}\n\nconst filteredProperties = computed(() => {\n  if (activeCategoryFilter.value === 'all') {\n    return PROPERTIES\n  }\n  return PROPERTIES.filter((p) => p.category === activeCategoryFilter.value)\n})\n</script>\n\n<template>\n  <section data-slot=\"property-listing-card\" :class=\"cn('bg-background text-foreground w-full space-y-6', props.class)\">\n    <!-- Header & Category Switcher -->\n    <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n      <div class=\"space-y-1\">\n        <div class=\"flex items-center gap-2.5\">\n          <h2 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">Featured Property Listings</h2>\n          <Badge wrap variant=\"secondary\" class=\"text-xs font-normal tabular-nums\">\n            {{ filteredProperties.length }} Active\n          </Badge>\n        </div>\n        <p class=\"text-muted-foreground text-xs sm:text-sm\">\n          Browse verified real estate properties with interactive photo galleries, mortgage calculations, and 3D\n          walkthroughs.\n        </p>\n      </div>\n\n      <!-- Category Filter Pills -->\n      <div class=\"border-border/80 bg-muted/40 flex flex-wrap items-center gap-1.5 rounded-lg border p-1\">\n        <button\n          type=\"button\"\n          class=\"focus-visible:ring-ring min-h-6 rounded-md px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          :class=\"\n            activeCategoryFilter === 'all'\n              ? 'bg-background text-foreground shadow-xs'\n              : 'text-muted-foreground hover:text-foreground'\n          \"\n          @click=\"activeCategoryFilter = 'all'\"\n        >\n          All Homes\n        </button>\n        <button\n          type=\"button\"\n          class=\"focus-visible:ring-ring min-h-6 rounded-md px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          :class=\"\n            activeCategoryFilter === 'single-family'\n              ? 'bg-background text-foreground shadow-xs'\n              : 'text-muted-foreground hover:text-foreground'\n          \"\n          @click=\"activeCategoryFilter = 'single-family'\"\n        >\n          Single Family\n        </button>\n        <button\n          type=\"button\"\n          class=\"focus-visible:ring-ring min-h-6 rounded-md px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          :class=\"\n            activeCategoryFilter === 'waterfront'\n              ? 'bg-background text-foreground shadow-xs'\n              : 'text-muted-foreground hover:text-foreground'\n          \"\n          @click=\"activeCategoryFilter = 'waterfront'\"\n        >\n          Waterfront\n        </button>\n        <button\n          type=\"button\"\n          class=\"focus-visible:ring-ring min-h-6 rounded-md px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n          :class=\"\n            activeCategoryFilter === 'penthouse'\n              ? 'bg-background text-foreground shadow-xs'\n              : 'text-muted-foreground hover:text-foreground'\n          \"\n          @click=\"activeCategoryFilter = 'penthouse'\"\n        >\n          Penthouse\n        </button>\n      </div>\n    </div>\n\n    <!-- Properties Grid -->\n    <div class=\"grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3\">\n      <Card\n        v-for=\"prop in filteredProperties\"\n        :key=\"prop.id\"\n        class=\"group border-border bg-card hover:border-border/80 relative flex flex-col justify-between overflow-hidden rounded-xl border shadow-xs transition-colors duration-300 hover:shadow-md\"\n      >\n        <!-- 1. Image Carousel Gallery Hero (16:9 Aspect Ratio) -->\n        <div class=\"bg-muted relative aspect-video w-full overflow-hidden\">\n          <!-- Active Image -->\n          <img\n            :src=\"prop.images[getPhotoIndex(prop.id)]\"\n            :alt=\"`${prop.title} - Photo ${getPhotoIndex(prop.id) + 1}`\"\n            class=\"size-full object-cover object-center transition-transform duration-500 ease-out group-hover:scale-105\"\n            loading=\"lazy\"\n          />\n\n          <!-- Gradient Overlays for High-Contrast Readable Badges -->\n          <div class=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/75 via-black/15 to-black/60\" />\n\n          <!-- Top Overlay: Badges and Wishlist Button -->\n          <div class=\"absolute inset-x-3 top-3 z-10 flex items-start justify-between gap-2\">\n            <div class=\"flex flex-wrap items-center gap-1.5\">\n              <Badge wrap :variant=\"prop.badge.variant\" class=\"text-xs font-semibold shadow-xs\">\n                {{ prop.badge.label }}\n              </Badge>\n              <button\n                v-if=\"prop.hasVirtualTour\"\n                type=\"button\"\n                class=\"inline-flex min-h-6 items-center gap-1 rounded-full border border-white/25 bg-black/60 px-2.5 py-0.5 text-xs font-medium text-white shadow-xs backdrop-blur-md transition-colors hover:bg-black/80 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none\"\n                :title=\"activeTourModalId === prop.id ? 'Exit 3D Tour Mode' : 'Explore 3D Virtual Tour'\"\n                @click.stop=\"toggleVirtualTour(prop.id)\"\n              >\n                <Box class=\"text-success size-3\" />\n                <span>Virtual Tour 3D</span>\n              </button>\n            </div>\n\n            <!-- Wishlist Heart Button -->\n            <button\n              type=\"button\"\n              aria-label=\"Save property to wishlist\"\n              class=\"flex size-8 shrink-0 items-center justify-center rounded-full border border-white/20 bg-black/50 text-white shadow-xs backdrop-blur-md transition-colors hover:bg-black/75 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none\"\n              :class=\"isWishlisted(prop.id) ? 'text-destructive hover:text-destructive' : 'text-white'\"\n              @click.stop=\"toggleWishlist(prop.id)\"\n            >\n              <Heart class=\"size-4\" :class=\"isWishlisted(prop.id) ? 'fill-destructive text-destructive' : ''\" />\n            </button>\n          </div>\n\n          <!-- Chevron Carousel Controls (Appear on hover or focus) -->\n          <button\n            type=\"button\"\n            aria-label=\"Previous property photo\"\n            class=\"absolute top-1/2 left-2.5 z-10 flex size-8 -translate-y-1/2 items-center justify-center rounded-full border border-white/20 bg-black/55 text-white opacity-0 shadow-xs backdrop-blur-md transition-colors group-hover:opacity-100 hover:bg-black/80 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none\"\n            @click.stop=\"prevPhoto(prop.id, prop.images.length)\"\n          >\n            <ChevronLeft class=\"size-4\" />\n          </button>\n          <button\n            type=\"button\"\n            aria-label=\"Next property photo\"\n            class=\"absolute top-1/2 right-2.5 z-10 flex size-8 -translate-y-1/2 items-center justify-center rounded-full border border-white/20 bg-black/55 text-white opacity-0 shadow-xs backdrop-blur-md transition-colors group-hover:opacity-100 hover:bg-black/80 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none\"\n            @click.stop=\"nextPhoto(prop.id, prop.images.length)\"\n          >\n            <ChevronRight class=\"size-4\" />\n          </button>\n\n          <!-- Bottom Hero Overlay: Photo Counter, Dots, and Open House Tag -->\n          <div class=\"absolute inset-x-3 bottom-3 z-10 flex items-center justify-between gap-2\">\n            <!-- Photo Counter Pill -->\n            <span\n              class=\"inline-flex items-center gap-1.5 rounded-full border border-white/20 bg-black/60 px-2.5 py-0.5 text-xs font-medium text-white tabular-nums backdrop-blur-md\"\n            >\n              <Camera class=\"size-3 text-white/80\" />\n              {{ getPhotoIndex(prop.id) + 1 }} / {{ prop.totalPhotos }} Photos\n            </span>\n\n            <!-- Image Dots / Indicators -->\n            <div class=\"flex items-center gap-1 rounded-full bg-black/40 px-2 py-1 backdrop-blur-xs\">\n              <button\n                v-for=\"(_, index) in prop.images\"\n                :key=\"index\"\n                type=\"button\"\n                :aria-label=\"`Jump to photo ${index + 1}`\"\n                class=\"rounded-full transition-colors focus-visible:ring-1 focus-visible:ring-white focus-visible:outline-none\"\n                :class=\"\n                  getPhotoIndex(prop.id) === index ? 'h-1.5 w-3.5 bg-white' : 'size-1.5 bg-white/50 hover:bg-white/80'\n                \"\n                @click.stop=\"setPhoto(prop.id, index)\"\n              />\n            </div>\n          </div>\n\n          <!-- Virtual Tour Active Banner Callout -->\n          <div\n            v-if=\"activeTourModalId === prop.id\"\n            class=\"absolute inset-0 z-20 flex flex-col items-center justify-center bg-black/85 p-4 text-center backdrop-blur-sm transition-colors\"\n          >\n            <Compass class=\"text-success size-8 animate-spin\" />\n            <h4 class=\"mt-2 text-sm font-semibold text-white\">3D Virtual Space Initialized</h4>\n            <p class=\"mt-1 max-w-xs text-xs text-white/80\">\n              Interactive 360° LiDAR spatial model active for {{ prop.address.street }}.\n            </p>\n            <Button size=\"sm\" variant=\"secondary\" class=\"mt-3 text-xs\" @click.stop=\"activeTourModalId = null\">\n              Close 3D View\n            </Button>\n          </div>\n        </div>\n\n        <!-- 2. Card Content & Specifications -->\n        <CardContent class=\"flex flex-1 flex-col justify-between space-y-4 p-5\">\n          <div class=\"space-y-3.5\">\n            <!-- Price & Mortgage Estimate Row -->\n            <div class=\"flex flex-wrap items-start justify-between gap-3\">\n              <div class=\"space-y-1\">\n                <div class=\"flex items-baseline gap-2\">\n                  <span class=\"text-foreground text-2xl font-bold tracking-tight tabular-nums\">\n                    {{ formatPrice(prop.price) }}\n                  </span>\n                </div>\n                <div class=\"text-muted-foreground flex items-center gap-1.5 text-xs font-normal tabular-nums\">\n                  <Calculator class=\"text-muted-foreground/80 size-3.5 shrink-0\" />\n                  <span>{{ prop.mortgageEst }}</span>\n                </div>\n              </div>\n              <Badge wrap variant=\"secondary\" class=\"shrink-0 text-xs font-normal\">\n                {{ prop.propertyType }}\n              </Badge>\n            </div>\n\n            <!-- Specs Pill Grid (Beds, Baths, SqFt, $/SqFt) -->\n            <div class=\"grid grid-cols-4 gap-1.5 py-0.5\">\n              <!-- Beds -->\n              <div\n                class=\"border-border/60 bg-muted/40 flex flex-col items-center justify-center rounded-lg border p-2 text-center\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-1\">\n                  <BedDouble class=\"size-3.5\" />\n                  <span class=\"text-foreground text-xs font-semibold tabular-nums\">{{ prop.specs.beds }}</span>\n                </div>\n                <span class=\"text-muted-foreground text-xs\">Beds</span>\n              </div>\n\n              <!-- Baths -->\n              <div\n                class=\"border-border/60 bg-muted/40 flex flex-col items-center justify-center rounded-lg border p-2 text-center\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-1\">\n                  <Bath class=\"size-3.5\" />\n                  <span class=\"text-foreground text-xs font-semibold tabular-nums\">{{ prop.specs.baths }}</span>\n                </div>\n                <span class=\"text-muted-foreground text-xs\">Baths</span>\n              </div>\n\n              <!-- Sq Ft -->\n              <div\n                class=\"border-border/60 bg-muted/40 flex flex-col items-center justify-center rounded-lg border p-2 text-center\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-1\">\n                  <Maximize2 class=\"size-3.5\" />\n                  <span class=\"text-foreground text-xs font-semibold tabular-nums\">\n                    {{ prop.specs.sqft.toLocaleString() }}\n                  </span>\n                </div>\n                <span class=\"text-muted-foreground text-xs\">Sq Ft</span>\n              </div>\n\n              <!-- Price Per Sq Ft -->\n              <div\n                class=\"border-border/60 bg-muted/40 flex flex-col items-center justify-center rounded-lg border p-2 text-center\"\n              >\n                <div class=\"text-muted-foreground flex items-center gap-0.5\">\n                  <span class=\"text-foreground text-xs font-semibold tabular-nums\">${{ prop.specs.pricePerSqft }}</span>\n                </div>\n                <span class=\"text-muted-foreground text-xs\">/ sqft</span>\n              </div>\n            </div>\n\n            <!-- Address & Neighborhood -->\n            <div class=\"space-y-1\">\n              <h3\n                class=\"text-foreground hover:text-primary line-clamp-1 cursor-pointer text-sm font-semibold tracking-tight transition-colors\"\n                :title=\"prop.address.street\"\n              >\n                {{ prop.address.street }}\n              </h3>\n              <p class=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n                <MapPin class=\"text-muted-foreground/80 size-3.5 shrink-0\" />\n                <span class=\"truncate\">{{ prop.address.cityStateZip }}</span>\n                <span>·</span>\n                <span class=\"text-foreground/85 shrink-0 font-medium\">{{ prop.address.neighborhood }}</span>\n              </p>\n            </div>\n\n            <!-- Key Features Tags -->\n            <div class=\"flex flex-wrap gap-1.5 pt-0.5\">\n              <Badge\n                wrap\n                v-for=\"tag in prop.tags\"\n                :key=\"tag\"\n                variant=\"outline\"\n                class=\"border-border/70 bg-muted/30 text-muted-foreground hover:bg-muted/60 text-xs font-normal transition-colors\"\n              >\n                {{ tag }}\n              </Badge>\n            </div>\n          </div>\n\n          <!-- Footer Area: Agent Attribution & Action Buttons -->\n          <div class=\"space-y-3.5 pt-2\">\n            <Separator />\n\n            <!-- Agent / Broker Attribution -->\n            <div class=\"flex items-center justify-between gap-3\">\n              <div class=\"flex min-w-0 items-center gap-2.5\">\n                <Avatar size=\"sm\" class=\"border-border size-8 border shadow-2xs\">\n                  <AvatarImage :src=\"prop.agent.avatar\" :alt=\"prop.agent.name\" />\n                  <AvatarFallback class=\"bg-primary/10 text-primary text-xs font-semibold\">\n                    {{ prop.agent.initials }}\n                  </AvatarFallback>\n                </Avatar>\n                <div class=\"min-w-0\">\n                  <p class=\"text-foreground flex items-center gap-1 truncate text-xs font-semibold\">\n                    <span>Listed by {{ prop.agent.name }}</span>\n                    <BadgeCheck class=\"text-primary size-3 shrink-0\" />\n                  </p>\n                  <p class=\"text-muted-foreground truncate text-xs\">{{ prop.agent.brokerage }}</p>\n                </div>\n              </div>\n\n              <!-- Share Action Button -->\n              <Button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-8 shrink-0 rounded-full\"\n                :title=\"copiedLinkId === prop.id ? 'Listing link copied!' : 'Share listing'\"\n                @click=\"shareListing(prop.id, prop.address.street)\"\n              >\n                <Check v-if=\"copiedLinkId === prop.id\" class=\"text-success size-3.5\" />\n                <Share2 v-else class=\"size-3.5\" />\n                <span class=\"sr-only\">Share listing</span>\n              </Button>\n            </div>\n\n            <!-- Action Buttons: Schedule Tour & Contact Agent -->\n            <div class=\"grid grid-cols-2 gap-2\">\n              <Button\n                size=\"sm\"\n                class=\"w-full gap-1.5 text-xs font-medium shadow-xs\"\n                :variant=\"isTourScheduled(prop.id) ? 'secondary' : 'default'\"\n                @click=\"toggleScheduleTour(prop.id)\"\n              >\n                <Check v-if=\"isTourScheduled(prop.id)\" class=\"text-success size-3.5\" />\n                <Calendar v-else class=\"size-3.5\" />\n                <span>{{ isTourScheduled(prop.id) ? 'Tour Booked ✓' : 'Schedule Tour' }}</span>\n              </Button>\n\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"w-full gap-1.5 text-xs font-medium\"\n                :class=\"isAgentContacted(prop.id) ? 'border-success/50 bg-success/10 text-success' : ''\"\n                @click=\"toggleContactAgent(prop.id)\"\n              >\n                <Check v-if=\"isAgentContacted(prop.id)\" class=\"text-success size-3.5\" />\n                <Mail v-else class=\"size-3.5\" />\n                <span>{{ isAgentContacted(prop.id) ? 'Message Sent' : 'Contact Agent' }}</span>\n              </Button>\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/PropertyListingCard.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/avatar.json",
    "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": "Zillow and Redfin style real estate property listing card featuring interactive photo carousels with 16:9 aspect ratios, price and mortgage estimation, specification pills (beds, baths, square footage, price/sqft), address, key feature tags, listing agent attribution, and direct tour scheduling actions.",
  "categories": [
    "real-estate",
    "hospitality",
    "marketing",
    "ecommerce"
  ]
}