{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "property-listing-card",
  "title": "Property Listing Card",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/property-listing-card/property-listing-card.component.ts",
      "content": "// Zillow and Redfin style real estate property listing card with interactive photo\n// carousels, price and mortgage estimates, spec pills, tags, agent attribution, and tour\n// scheduling actions. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\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  LucideAngularModule,\n  Mail,\n  MapPin,\n  Maximize2,\n  Share2,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\n\nexport interface 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: { street: string; cityStateZip: string; neighborhood: string }\n  specs: { beds: number; baths: number; sqft: number; pricePerSqft: number; yearBuilt: number; lotSize: string }\n  badge: { label: string; variant: 'default' | 'secondary' | 'outline' | 'destructive' }\n  hasVirtualTour: boolean\n  openHouse: string\n  totalPhotos: number\n  images: string[]\n  tags: string[]\n  agent: { name: string; brokerage: string; avatar: string; initials: string; badge: string }\n}\n\nexport const 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: { beds: 4, baths: 3.5, sqft: 2850, pricePerSqft: 437, yearBuilt: 1962, lotSize: '0.34 Acres' },\n    badge: { label: 'New Listing', variant: 'default' },\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: { beds: 5, baths: 4.5, sqft: 4120, pricePerSqft: 701, yearBuilt: 2021, lotSize: '0.52 Acres' },\n    badge: { label: 'Price Drop $100k', variant: 'secondary' },\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: { beds: 2, baths: 2.0, sqft: 1640, pricePerSqft: 545, yearBuilt: 2019, lotSize: 'HOA $620 / mo' },\n    badge: { label: 'Just Listed', variant: 'default' },\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\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-property-listing-card, [ui-property-listing-card]',\n  standalone: true,\n  host: {\n    '[attr.data-slot]': '\"property-listing-card\"',\n    '[class]': 'hostClass',\n  },\n  imports: [\n    LucideAngularModule,\n    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiAvatarImageComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\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          <span ui-badge variant=\"secondary\" class=\"text-xs font-normal tabular-nums\">\n            {{ filteredProperties().length }} Active\n          </span>\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        @for (f of categoryFilters; track f.value) {\n          <button type=\"button\" [class]=\"filterPillClass(f.value)\" (click)=\"activeCategoryFilter.set(f.value)\">\n            {{ f.label }}\n          </button>\n        }\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      @for (prop of filteredProperties(); track prop.id) {\n        <div\n          ui-card\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            <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            <div\n              class=\"pointer-events-none absolute inset-0 bg-gradient-to-t from-black/75 via-black/15 to-black/60\"\n            ></div>\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                <span ui-badge [variant]=\"prop.badge.variant\" class=\"text-xs font-semibold shadow-xs\">\n                  {{ prop.badge.label }}\n                </span>\n                @if (prop.hasVirtualTour) {\n                  <button\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)=\"toggleVirtualTour(prop.id); $event.stopPropagation()\"\n                  >\n                    <lucide-icon [img]=\"Box\" class=\"text-success size-3\" />\n                    <span>Virtual Tour 3D</span>\n                  </button>\n                }\n              </div>\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)=\"toggleWishlist(prop.id); $event.stopPropagation()\"\n              >\n                <lucide-icon\n                  [img]=\"Heart\"\n                  class=\"size-4\"\n                  [class]=\"isWishlisted(prop.id) ? 'fill-destructive text-destructive' : ''\"\n                />\n              </button>\n            </div>\n\n            <!-- Chevron Carousel Controls -->\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)=\"prevPhoto(prop.id, prop.images.length); $event.stopPropagation()\"\n            >\n              <lucide-icon [img]=\"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)=\"nextPhoto(prop.id, prop.images.length); $event.stopPropagation()\"\n            >\n              <lucide-icon [img]=\"ChevronRight\" class=\"size-4\" />\n            </button>\n\n            <!-- Bottom Hero Overlay -->\n            <div class=\"absolute inset-x-3 bottom-3 z-10 flex items-center justify-between gap-2\">\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                <lucide-icon [img]=\"Camera\" class=\"size-3 text-white/80\" />\n                {{ getPhotoIndex(prop.id) + 1 }} / {{ prop.totalPhotos }} Photos\n              </span>\n              <div class=\"flex items-center gap-1 rounded-full bg-black/40 px-2 py-1 backdrop-blur-xs\">\n                @for (img of prop.images; track $index) {\n                  <button\n                    type=\"button\"\n                    [attr.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\n                        ? 'h-1.5 w-3.5 bg-white'\n                        : 'size-1.5 bg-white/50 hover:bg-white/80'\n                    \"\n                    (click)=\"setPhoto(prop.id, $index); $event.stopPropagation()\"\n                  ></button>\n                }\n              </div>\n            </div>\n\n            @if (activeTourModalId() === prop.id) {\n              <div\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                <lucide-icon [img]=\"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\n                  ui-button\n                  size=\"sm\"\n                  variant=\"secondary\"\n                  class=\"mt-3 text-xs\"\n                  (click)=\"activeTourModalId.set(null); $event.stopPropagation()\"\n                >\n                  Close 3D View\n                </button>\n              </div>\n            }\n          </div>\n\n          <!-- 2. Card Content & Specifications -->\n          <div ui-card-content class=\"flex flex-1 flex-col justify-between space-y-4 p-5\">\n            <div class=\"space-y-3.5\">\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                    <lucide-icon [img]=\"Calculator\" class=\"text-muted-foreground/80 size-3.5 shrink-0\" />\n                    <span>{{ prop.mortgageEst }}</span>\n                  </div>\n                </div>\n                <span ui-badge variant=\"secondary\" class=\"shrink-0 text-xs font-normal\">\n                  {{ prop.propertyType }}\n                </span>\n              </div>\n\n              <div class=\"grid grid-cols-4 gap-1.5 py-0.5\">\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                    <lucide-icon [img]=\"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                <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                    <lucide-icon [img]=\"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                <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                    <lucide-icon [img]=\"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                <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\"\n                      >\\${{ prop.specs.pricePerSqft }}</span\n                    >\n                  </div>\n                  <span class=\"text-muted-foreground text-xs\">/ sqft</span>\n                </div>\n              </div>\n\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                  <lucide-icon [img]=\"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              <div class=\"flex flex-wrap gap-1.5 pt-0.5\">\n                @for (tag of prop.tags; track tag) {\n                  <span\n                    ui-badge\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                  </span>\n                }\n              </div>\n            </div>\n\n            <div class=\"space-y-3.5 pt-2\">\n              <ui-separator />\n              <div class=\"flex items-center justify-between gap-3\">\n                <div class=\"flex min-w-0 items-center gap-2.5\">\n                  <ui-avatar size=\"sm\" class=\"border-border size-8 border shadow-2xs\">\n                    <ui-avatar-image [src]=\"prop.agent.avatar\" [alt]=\"prop.agent.name\" />\n                    <ui-avatar-fallback class=\"bg-primary/10 text-primary text-xs font-semibold\">\n                      {{ prop.agent.initials }}\n                    </ui-avatar-fallback>\n                  </ui-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                      <lucide-icon [img]=\"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                <button\n                  ui-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)\"\n                >\n                  @if (copiedLinkId() === prop.id) {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                  } @else {\n                    <lucide-icon [img]=\"Share2\" class=\"size-3.5\" />\n                  }\n                  <span class=\"sr-only\">Share listing</span>\n                </button>\n              </div>\n\n              <div class=\"grid grid-cols-2 gap-2\">\n                <button\n                  ui-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                  @if (isTourScheduled(prop.id)) {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                  } @else {\n                    <lucide-icon [img]=\"Calendar\" class=\"size-3.5\" />\n                  }\n                  <span>{{ isTourScheduled(prop.id) ? 'Tour Booked ✓' : 'Schedule Tour' }}</span>\n                </button>\n                <button\n                  ui-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                  @if (isAgentContacted(prop.id)) {\n                    <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                  } @else {\n                    <lucide-icon [img]=\"Mail\" class=\"size-3.5\" />\n                  }\n                  <span>{{ isAgentContacted(prop.id) ? 'Message Sent' : 'Contact Agent' }}</span>\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      }\n    </div>\n  `,\n})\nexport class UiPropertyListingCardComponent {\n  protected readonly BadgeCheck = BadgeCheck\n  protected readonly Bath = Bath\n  protected readonly BedDouble = BedDouble\n  protected readonly Box = Box\n  protected readonly Calculator = Calculator\n  protected readonly Calendar = Calendar\n  protected readonly Camera = Camera\n  protected readonly Check = Check\n  protected readonly ChevronLeft = ChevronLeft\n  protected readonly ChevronRight = ChevronRight\n  protected readonly Compass = Compass\n  protected readonly Heart = Heart\n  protected readonly Mail = Mail\n  protected readonly MapPin = MapPin\n  protected readonly Maximize2 = Maximize2\n  protected readonly Share2 = Share2\n\n  @Input('class') className?: string\n  @Input() set initialCategory(v: string) {\n    this.activeCategoryFilter.set(v ?? 'all')\n  }\n\n  protected readonly categoryFilters = [\n    { value: 'all', label: 'All Homes' },\n    { value: 'single-family', label: 'Single Family' },\n    { value: 'waterfront', label: 'Waterfront' },\n    { value: 'penthouse', label: 'Penthouse' },\n  ]\n\n  readonly activePhotoIndex = signal<Record<string, number>>({\n    'prop-pasadena': 0,\n    'prop-carmel': 0,\n    'prop-downtown': 0,\n  })\n  readonly wishlistedIds = signal<Set<string>>(new Set(['prop-pasadena']))\n  readonly scheduledTourIds = signal<Set<string>>(new Set())\n  readonly contactedAgentIds = signal<Set<string>>(new Set())\n  readonly copiedLinkId = signal<string | null>(null)\n  readonly activeCategoryFilter = signal<string>('all')\n  readonly activeTourModalId = signal<string | null>(null)\n\n  readonly filteredProperties = computed(() => {\n    const f = this.activeCategoryFilter()\n    if (f === 'all') return PROPERTIES\n    return PROPERTIES.filter((p) => p.category === f)\n  })\n\n  get hostClass(): string {\n    return cn('bg-background text-foreground w-full space-y-6', this.className)\n  }\n\n  filterPillClass(value: string): string {\n    return cn(\n      '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      this.activeCategoryFilter() === value\n        ? 'bg-background text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  getPhotoIndex(id: string): number {\n    return this.activePhotoIndex()[id] ?? 0\n  }\n\n  prevPhoto(id: string, total: number): void {\n    const current = this.getPhotoIndex(id)\n    this.activePhotoIndex.update((prev) => ({ ...prev, [id]: (current - 1 + total) % total }))\n  }\n\n  nextPhoto(id: string, total: number): void {\n    const current = this.getPhotoIndex(id)\n    this.activePhotoIndex.update((prev) => ({ ...prev, [id]: (current + 1) % total }))\n  }\n\n  setPhoto(id: string, index: number): void {\n    this.activePhotoIndex.update((prev) => ({ ...prev, [id]: index }))\n  }\n\n  toggleWishlist(id: string): void {\n    this.wishlistedIds.update((prev) => {\n      const next = new Set(prev)\n      if (next.has(id)) next.delete(id)\n      else next.add(id)\n      return next\n    })\n  }\n\n  isWishlisted(id: string): boolean {\n    return this.wishlistedIds().has(id)\n  }\n\n  toggleScheduleTour(id: string): void {\n    this.scheduledTourIds.update((prev) => {\n      const next = new Set(prev)\n      if (next.has(id)) next.delete(id)\n      else next.add(id)\n      return next\n    })\n  }\n\n  isTourScheduled(id: string): boolean {\n    return this.scheduledTourIds().has(id)\n  }\n\n  toggleContactAgent(id: string): void {\n    this.contactedAgentIds.update((prev) => {\n      const next = new Set(prev)\n      if (next.has(id)) next.delete(id)\n      else next.add(id)\n      return next\n    })\n  }\n\n  isAgentContacted(id: string): boolean {\n    return this.contactedAgentIds().has(id)\n  }\n\n  shareListing(id: string): void {\n    const url = `https://uipkge.dev/listings/${id}`\n    try {\n      void navigator?.clipboard?.writeText?.(url)\n    } catch {\n      // clipboard unavailable in test env\n    }\n    this.copiedLinkId.set(id)\n    setTimeout(() => {\n      if (this.copiedLinkId() === id) this.copiedLinkId.set(null)\n    }, 2000)\n  }\n\n  toggleVirtualTour(id: string): void {\n    this.activeTourModalId.update((curr) => (curr === id ? null : id))\n  }\n\n  formatPrice(amount: number): string {\n    return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(\n      amount,\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/property-listing-card/property-listing-card.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/property-listing-card/index.ts",
      "content": "export { UiPropertyListingCardComponent, PROPERTIES, type PropertyItem } from './property-listing-card.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/property-listing-card/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/avatar.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/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"
  ]
}