{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "leaflet-disaster-response-map",
  "title": "Leaflet Disaster Response Map",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/leaflet-disaster-response-map/leaflet-disaster-response-map.component.ts",
      "content": "// Incident management map on free OpenStreetMap/Esri tiles — no API key. Evacuation\n// perimeter rings, shelter capacities, and emergency staging points. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, ViewChild, signal } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiLeafletCircleComponent,\n  UiLeafletMapComponent,\n  UiLeafletMarkerComponent,\n} from '@/ui/leaflet-map/leaflet-map.component'\n\nexport type FacilityType = 'Shelter' | 'Medical Staging' | 'Command Post'\nexport type FacilityStatus = 'Open' | 'Near Capacity' | 'Closed'\n\nexport interface Facility {\n  id: string\n  name: string\n  type: FacilityType\n  /** [lng, lat] — Mapbox order. */\n  coords: [number, number]\n  capacity: number\n  occupied: number\n  status: FacilityStatus\n}\n\n// Module-level constants: stable references keep LeafletMap's center effect\n// from re-firing setView on every render and cancelling flyTo animations.\nconst MAP_CENTER: [number, number] = [-122.2711, 37.8044]\n\n// Wildfire incident origin in the Oakland Hills + a secondary spot fire.\nconst INCIDENT: [number, number] = [-122.165, 37.845]\nconst SPOT_FIRE: [number, number] = [-122.145, 37.856]\n\n// Evacuation perimeter rings (meter radii): hard exclusion + warning zone.\nconst EVAC_RADIUS_M = 1800\nconst WARNING_RADIUS_M = 3600\n\nexport const DEFAULT_FACILITIES: Facility[] = [\n  {\n    id: 's-1',\n    name: 'Oakland Civic Shelter',\n    type: 'Shelter',\n    coords: [-122.265, 37.795],\n    capacity: 500,\n    occupied: 340,\n    status: 'Open',\n  },\n  {\n    id: 's-2',\n    name: 'Eastmont Medical Staging',\n    type: 'Medical Staging',\n    coords: [-122.19, 37.77],\n    capacity: 120,\n    occupied: 98,\n    status: 'Near Capacity',\n  },\n  {\n    id: 's-3',\n    name: 'Unified Incident Command Post',\n    type: 'Command Post',\n    coords: [-122.225, 37.852],\n    capacity: 80,\n    occupied: 45,\n    status: 'Open',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-leaflet-disaster-response-map, [ui-leaflet-disaster-response-map]',\n  standalone: true,\n  imports: [\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiLeafletCircleComponent,\n    UiLeafletMapComponent,\n    UiLeafletMarkerComponent,\n  ],\n  host: {\n    'data-slot': 'leaflet-disaster-response-map',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <div class=\"border-border bg-card relative h-[480px] overflow-hidden rounded-xl border\">\n      <ui-leaflet-map variant=\"light\" [center]=\"mapCenter\" [zoom]=\"11\" class=\"h-full w-full\">\n        <!-- Evacuation perimeter rings (radii in meters) -->\n        <ui-leaflet-circle\n          [center]=\"incident\"\n          [radius]=\"evacRadiusM\"\n          color=\"#dc2626\"\n          [weight]=\"2\"\n          [opacity]=\"0.9\"\n          dashArray=\"6 4\"\n          fillColor=\"#ef4444\"\n          [fillOpacity]=\"0.15\"\n        />\n        <ui-leaflet-circle\n          [center]=\"incident\"\n          [radius]=\"warningRadiusM\"\n          color=\"#d97706\"\n          [weight]=\"1.5\"\n          [opacity]=\"0.7\"\n          dashArray=\"4 6\"\n          fillColor=\"#f59e0b\"\n          [fillOpacity]=\"0.05\"\n        />\n\n        <!-- Active fire origin -->\n        <ui-leaflet-marker [lngLat]=\"incident\" anchor=\"center\">\n          <div class=\"relative flex cursor-pointer items-center justify-center\" (click)=\"focusHazard()\">\n            <span class=\"bg-destructive/30 absolute size-9 animate-ping rounded-full\"></span>\n            <div\n              class=\"bg-destructive flex size-7 items-center justify-center rounded-full font-mono text-xs font-bold text-white shadow-lg ring-2 ring-white\"\n            >\n              🔥\n            </div>\n          </div>\n        </ui-leaflet-marker>\n\n        <!-- Secondary spot fire -->\n        <ui-leaflet-marker [lngLat]=\"spotFire\" anchor=\"center\">\n          <div class=\"relative flex cursor-pointer items-center justify-center\" (click)=\"focusHazard()\">\n            <span class=\"bg-warning/30 absolute size-6 animate-ping rounded-full\"></span>\n            <div\n              class=\"bg-warning flex size-6 items-center justify-center rounded-full font-mono text-xs font-bold text-white shadow-md ring-2 ring-white\"\n            >\n              🔥\n            </div>\n          </div>\n        </ui-leaflet-marker>\n\n        <!-- Shelters and Medical Staging -->\n        @for (f of facilities; track f.id) {\n          <ui-leaflet-marker [lngLat]=\"f.coords\" anchor=\"bottom\">\n            <div class=\"group flex cursor-pointer flex-col items-center\" (click)=\"focusFacility(f)\">\n              <div [class]=\"facilityPinClass(f)\">\n                {{ facilityGlyph(f) }}\n              </div>\n              <span\n                class=\"border-border bg-background/90 mt-1 rounded border px-1 py-0.5 font-mono text-xs font-bold whitespace-nowrap shadow-xs\"\n              >\n                {{ shortName(f) }}\n              </span>\n            </div>\n          </ui-leaflet-marker>\n        }\n      </ui-leaflet-map>\n    </div>\n\n    <!-- Incident Control Dashboard -->\n    <ui-card class=\"flex flex-col justify-between\">\n      <ui-card-header>\n        <div class=\"flex items-center justify-between\">\n          <span\n            ui-badge\n            variant=\"outline\"\n            class=\"border-destructive/30 bg-destructive/10 text-destructive font-mono text-xs\"\n          >\n            LEVEL 3 EVACUATION\n          </span>\n          <span ui-badge class=\"border-success/20 bg-success/10 text-success\">{{ activeFacility().status }}</span>\n        </div>\n        <ui-card-title class=\"mt-2 text-lg\">{{ activeFacility().name }}</ui-card-title>\n        <ui-card-description class=\"text-xs\">\n          Capacity: {{ activeFacility().occupied }} / {{ activeFacility().capacity }} beds occupied\n        </ui-card-description>\n      </ui-card-header>\n      <ui-card-content class=\"space-y-4\">\n        <!-- Capacity Bar -->\n        <div class=\"border-border space-y-1.5 border-t pt-3\">\n          <div class=\"flex items-center justify-between font-mono text-xs\">\n            <span class=\"text-muted-foreground\">Occupancy Rate</span>\n            <span class=\"text-foreground font-bold\"> {{ occupancyPct(activeFacility()) }}% </span>\n          </div>\n          <div class=\"bg-muted h-2 w-full overflow-hidden rounded-full\">\n            <div [class]=\"occupancyBarClass(activeFacility())\" [style.width.%]=\"occupancyPct(activeFacility())\"></div>\n          </div>\n        </div>\n\n        <div class=\"border-border space-y-1 border-t pt-3\">\n          <div class=\"text-muted-foreground mb-1 font-mono text-xs tracking-wider uppercase\">Staged Relief Sites</div>\n          @for (f of facilities; track f.id) {\n            <button type=\"button\" [class]=\"rowClass(f)\" (click)=\"focusFacility(f)\">\n              <span class=\"max-w-[170px] truncate\">{{ f.name }}</span>\n              <span class=\"font-mono text-xs opacity-75\"> {{ f.occupied }}/{{ f.capacity }} </span>\n            </button>\n          }\n        </div>\n\n        <div class=\"flex gap-2 pt-2\">\n          <button ui-button variant=\"outline\" size=\"sm\" class=\"flex-1 font-mono text-xs\" (click)=\"focusHazard()\">\n            Exclusion Zone\n          </button>\n          <button ui-button variant=\"outline\" size=\"sm\" class=\"flex-1 font-mono text-xs\" (click)=\"focusActive()\">\n            Focus Site\n          </button>\n        </div>\n      </ui-card-content>\n    </ui-card>\n  `,\n})\nexport class UiLeafletDisasterResponseMapComponent {\n  @Input() facilities: Facility[] = DEFAULT_FACILITIES\n  @Input('class') className?: string\n\n  @ViewChild(UiLeafletMapComponent) map?: UiLeafletMapComponent\n\n  protected readonly mapCenter = MAP_CENTER\n  protected readonly incident = INCIDENT\n  protected readonly spotFire = SPOT_FIRE\n  protected readonly evacRadiusM = EVAC_RADIUS_M\n  protected readonly warningRadiusM = WARNING_RADIUS_M\n\n  readonly activeId = signal(DEFAULT_FACILITIES[0]?.id ?? '')\n\n  get hostClass(): string {\n    return cn('grid gap-4 lg:grid-cols-[1fr_320px]', this.className)\n  }\n\n  activeFacility(): Facility {\n    return this.facilities.find((f) => f.id === this.activeId()) ?? this.facilities[0]!\n  }\n\n  focusFacility(f: Facility): void {\n    this.activeId.set(f.id)\n    this.map?.flyTo?.({ center: f.coords, zoom: 12.5, duration: 800 })\n  }\n\n  focusActive(): void {\n    this.focusFacility(this.activeFacility())\n  }\n\n  focusHazard(): void {\n    this.map?.flyTo?.({ center: INCIDENT, zoom: 12, duration: 800 })\n  }\n\n  occupancyPct(f: Facility): number {\n    return Math.round((f.occupied / f.capacity) * 100)\n  }\n\n  occupancyRate(f: Facility): number {\n    return f.occupied / f.capacity\n  }\n\n  occupancyBarClass(f: Facility): string {\n    return cn(\n      'h-full rounded-full transition-[width,background-color]',\n      this.occupancyRate(f) > 0.8 ? 'bg-warning' : 'bg-success',\n    )\n  }\n\n  facilityPinClass(f: Facility): string {\n    return cn(\n      'flex size-7 items-center justify-center rounded-full shadow-md ring-2',\n      f.type === 'Shelter'\n        ? 'bg-success text-white ring-emerald-400/40'\n        : f.type === 'Medical Staging'\n          ? 'bg-info text-white ring-blue-400/40'\n          : 'bg-primary text-primary-foreground ring-primary/40',\n    )\n  }\n\n  facilityGlyph(f: Facility): string {\n    return f.type === 'Shelter' ? '🏠' : f.type === 'Medical Staging' ? '✚' : '★'\n  }\n\n  shortName(f: Facility): string {\n    return f.name.split(' ')[0] ?? f.name\n  }\n\n  rowClass(f: Facility): string {\n    return cn(\n      'flex w-full items-center justify-between rounded-md px-2 py-1 text-xs transition-colors',\n      this.activeId() === f.id\n        ? 'bg-accent text-accent-foreground font-semibold'\n        : 'hover:bg-muted text-muted-foreground',\n    )\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-disaster-response-map/leaflet-disaster-response-map.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/leaflet-disaster-response-map/index.ts",
      "content": "export {\n  UiLeafletDisasterResponseMapComponent,\n  DEFAULT_FACILITIES,\n  type Facility,\n  type FacilityType,\n  type FacilityStatus,\n} from './leaflet-disaster-response-map.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/leaflet-disaster-response-map/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "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/leaflet-map.json"
  ],
  "description": "Incident management map on free OpenStreetMap/Esri tiles — no API key. Evacuation perimeter rings, shelter capacities, and emergency staging points.",
  "categories": [
    "logistics",
    "emergency",
    "map"
  ]
}