{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "line-radial",
  "title": "Map Line Radial",
  "description": "Radial lines from a center point to multiple destinations with optional markers.",
  "dependencies": ["mapbox-gl"],
  "devDependencies": ["@types/mapbox-gl"],
  "registryDependencies": ["https://www.terrae.dev/map.json"],
  "files": [
    {
      "path": "src/registry/map/line-radial.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState, type ReactNode } from \"react\"\nimport { createPortal } from \"react-dom\"\nimport type mapboxgl from \"mapbox-gl\"\nimport { mapgl } from \"./map-library\"\nimport { useMap } from \"./hooks\"\nimport type { MapCoordinates } from \"./types\"\n\ntype LineCurvature = number | \"auto\"\n\ntype RadialDestination =\n  | MapCoordinates\n  | {\n      coordinates: MapCoordinates\n      color?: string\n      label?: string\n    }\n\ntype MapLineRadialProps = {\n  id: string\n  origin: MapCoordinates\n  destinations: RadialDestination[]\n  color?: string\n  width?: number\n  opacity?: number\n  dashArray?: [number, number]\n  curvature?: LineCurvature\n  curveSegments?: number\n  duration?: number\n  staggerDelay?: number\n  autoStart?: boolean\n  loop?: boolean\n  loopDelay?: number\n  showOriginMarker?: boolean\n  originMarkerColor?: string\n  originMarkerIcon?: ReactNode\n  originMarkerPulse?: boolean\n  showDestinationMarkers?: boolean\n  destinationMarkerColor?: string\n  destinationMarkerIcon?: ReactNode\n  showTravelingMarker?: boolean\n  travelingMarkerColor?: string\n  travelingMarkerIcon?: ReactNode\n  onLineComplete?: (index: number, destination: MapCoordinates) => void\n  onComplete?: () => void\n}\n\nconst DEFAULT_COLOR = \"#3b82f6\"\nconst DEFAULT_WIDTH = 2\nconst DEFAULT_OPACITY = 0.8\nconst DEFAULT_CURVATURE = 0.3\nconst DEFAULT_CURVE_SEGMENTS = 50\nconst DEFAULT_DURATION = 2000\nconst DEFAULT_STAGGER_DELAY = 200\nconst DEFAULT_AUTO_START = true\nconst DEFAULT_LOOP = false\nconst DEFAULT_LOOP_DELAY = 1000\nconst DEFAULT_SHOW_ORIGIN_MARKER = true\nconst DEFAULT_ORIGIN_MARKER_COLOR = \"#ef4444\"\nconst DEFAULT_ORIGIN_MARKER_PULSE = true\nconst DEFAULT_SHOW_DESTINATION_MARKERS = true\nconst DEFAULT_SHOW_TRAVELING_MARKER = false\n\nconst ORIGIN_MARKER_SIZE = 32\nconst ORIGIN_MARKER_RADIUS = 10\nconst DESTINATION_MARKER_SIZE = 24\nconst DESTINATION_MARKER_RADIUS = 6\nconst PULSE_RADIUS = 20\nconst TRAVELING_MARKER_RADIUS = 6\n\nconst getDestinationCoordinates = (destination: RadialDestination): MapCoordinates => {\n  if (Array.isArray(destination)) {\n    return destination\n  }\n  return destination.coordinates\n}\n\nconst getDestinationColor = (destination: RadialDestination): string | undefined => {\n  if (Array.isArray(destination)) {\n    return undefined\n  }\n  return destination.color\n}\n\nconst calculateCurvature = (\n  origin: MapCoordinates,\n  destination: MapCoordinates,\n  baseCurvature: LineCurvature\n): number => {\n  if (baseCurvature !== \"auto\") {\n    return baseCurvature\n  }\n\n  const deltaLongitude = destination[0] - origin[0]\n  const deltaLatitude = destination[1] - origin[1]\n  const distance = Math.sqrt(deltaLongitude * deltaLongitude + deltaLatitude * deltaLatitude)\n\n  return Math.min(0.5, Math.max(0.1, distance / 100))\n}\n\nconst generateCurvedPath = (\n  origin: MapCoordinates,\n  destination: MapCoordinates,\n  curvature: number,\n  segments: number\n): MapCoordinates[] => {\n  const path: MapCoordinates[] = []\n\n  const deltaLongitude = destination[0] - origin[0]\n  const deltaLatitude = destination[1] - origin[1]\n\n  const midpointLongitude = origin[0] + deltaLongitude / 2\n  const midpointLatitude = origin[1] + deltaLatitude / 2\n\n  const perpendicularLongitude = -deltaLatitude * curvature\n  const perpendicularLatitude = deltaLongitude * curvature\n\n  const controlPointLongitude = midpointLongitude + perpendicularLongitude\n  const controlPointLatitude = midpointLatitude + perpendicularLatitude\n\n  for (let segmentIndex = 0; segmentIndex <= segments; segmentIndex++) {\n    const interpolationFactor = segmentIndex / segments\n\n    const longitude =\n      (1 - interpolationFactor) * (1 - interpolationFactor) * origin[0] +\n      2 * (1 - interpolationFactor) * interpolationFactor * controlPointLongitude +\n      interpolationFactor * interpolationFactor * destination[0]\n    const latitude =\n      (1 - interpolationFactor) * (1 - interpolationFactor) * origin[1] +\n      2 * (1 - interpolationFactor) * interpolationFactor * controlPointLatitude +\n      interpolationFactor * interpolationFactor * destination[1]\n\n    path.push([longitude, latitude])\n  }\n\n  return path\n}\n\nexport const MapLineRadial = ({\n  id,\n  origin,\n  destinations,\n  color = DEFAULT_COLOR,\n  width = DEFAULT_WIDTH,\n  opacity = DEFAULT_OPACITY,\n  dashArray,\n  curvature = DEFAULT_CURVATURE,\n  curveSegments = DEFAULT_CURVE_SEGMENTS,\n  duration = DEFAULT_DURATION,\n  staggerDelay = DEFAULT_STAGGER_DELAY,\n  autoStart = DEFAULT_AUTO_START,\n  loop = DEFAULT_LOOP,\n  loopDelay = DEFAULT_LOOP_DELAY,\n  showOriginMarker = DEFAULT_SHOW_ORIGIN_MARKER,\n  originMarkerColor = DEFAULT_ORIGIN_MARKER_COLOR,\n  originMarkerIcon,\n  originMarkerPulse = DEFAULT_ORIGIN_MARKER_PULSE,\n  showDestinationMarkers = DEFAULT_SHOW_DESTINATION_MARKERS,\n  destinationMarkerColor,\n  destinationMarkerIcon,\n  showTravelingMarker = DEFAULT_SHOW_TRAVELING_MARKER,\n  travelingMarkerColor,\n  travelingMarkerIcon,\n  onLineComplete,\n  onComplete,\n}: MapLineRadialProps) => {\n  const { map, isLoaded } = useMap()\n\n  const initializedRef = useRef(false)\n  const styleLoadedRef = useRef(false)\n  const animationFrameRef = useRef<number | undefined>(undefined)\n  const loopTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n  const htmlOriginMarkerRef = useRef<mapboxgl.Marker | null>(null)\n  const originMarkerElementRef = useRef<HTMLDivElement | null>(null)\n  const htmlDestinationMarkersRef = useRef<mapboxgl.Marker[]>([])\n  const destinationMarkerElementsRef = useRef<HTMLDivElement[]>([])\n  const htmlTravelingMarkerRef = useRef<mapboxgl.Marker | null>(null)\n  const travelingMarkerElementRef = useRef<HTMLDivElement | null>(null)\n  const startTimeRef = useRef<number>(0)\n  const lineProgressRef = useRef<number[]>([])\n\n  const originRef = useRef(origin)\n  const destinationsRef = useRef(destinations)\n  const curvatureRef = useRef(curvature)\n  const curveSegmentsRef = useRef(curveSegments)\n  const durationRef = useRef(duration)\n  const staggerDelayRef = useRef(staggerDelay)\n  const showTravelingMarkerRef = useRef(showTravelingMarker)\n  const destinationMarkerIconRef = useRef(destinationMarkerIcon)\n  const travelingMarkerIconRef = useRef(travelingMarkerIcon)\n  const onLineCompleteRef = useRef(onLineComplete)\n  const onCompleteRef = useRef(onComplete)\n\n  originRef.current = origin\n  destinationsRef.current = destinations\n  curvatureRef.current = curvature\n  curveSegmentsRef.current = curveSegments\n  durationRef.current = duration\n  staggerDelayRef.current = staggerDelay\n  showTravelingMarkerRef.current = showTravelingMarker\n  destinationMarkerIconRef.current = destinationMarkerIcon\n  travelingMarkerIconRef.current = travelingMarkerIcon\n  onLineCompleteRef.current = onLineComplete\n  onCompleteRef.current = onComplete\n\n  const [isAnimating, setIsAnimating] = useState(false)\n  const [isOriginMarkerMounted, setIsOriginMarkerMounted] = useState(false)\n  const [isTravelingMarkerMounted, setIsTravelingMarkerMounted] = useState(false)\n  const [destinationMarkersMounted, setDestinationMarkersMounted] = useState<boolean[]>([])\n\n  const linesSourceId = `${id}-lines-source`\n  const linesLayerId = `${id}-lines`\n  const originMarkerSourceId = `${id}-origin-marker-source`\n  const originMarkerLayerId = `${id}-origin-marker`\n  const originPulseSourceId = `${id}-origin-pulse-source`\n  const originPulseLayerId = `${id}-origin-pulse`\n  const destinationMarkersSourceId = `${id}-destination-markers-source`\n  const destinationMarkersLayerId = `${id}-destination-markers`\n  const travelingMarkerSourceId = `${id}-traveling-marker-source`\n  const travelingMarkerLayerId = `${id}-traveling-marker`\n\n  useEffect(() => {\n    if (!map) {\n      return\n    }\n\n    const addLinesSource = (mapInstance: mapboxgl.Map) => {\n      if (mapInstance.getSource(linesSourceId)) {\n        return\n      }\n\n      mapInstance.addSource(linesSourceId, {\n        type: \"geojson\",\n        data: {\n          type: \"FeatureCollection\",\n          features: [],\n        },\n      })\n\n      mapInstance.addLayer({\n        id: linesLayerId,\n        type: \"line\",\n        source: linesSourceId,\n        layout: { \"line-join\": \"round\", \"line-cap\": \"round\" },\n        paint: {\n          \"line-color\": [\"coalesce\", [\"get\", \"color\"], color],\n          \"line-width\": width,\n          \"line-opacity\": opacity,\n          ...(dashArray && { \"line-dasharray\": dashArray }),\n        },\n      })\n    }\n\n    const addOriginMarker = (mapInstance: mapboxgl.Map) => {\n      if (!showOriginMarker) {\n        return\n      }\n\n      if (originMarkerIcon) {\n        if (htmlOriginMarkerRef.current) {\n          return\n        }\n\n        const markerElement = document.createElement(\"div\")\n        markerElement.style.width = `${ORIGIN_MARKER_SIZE}px`\n        markerElement.style.height = `${ORIGIN_MARKER_SIZE}px`\n        markerElement.style.borderRadius = \"50%\"\n        markerElement.style.backgroundColor = originMarkerColor\n        markerElement.style.display = \"flex\"\n        markerElement.style.alignItems = \"center\"\n        markerElement.style.justifyContent = \"center\"\n        markerElement.style.boxShadow = \"0 0 0 3px white\"\n\n        originMarkerElementRef.current = markerElement\n        htmlOriginMarkerRef.current = new mapgl.Marker(markerElement).setLngLat(originRef.current).addTo(mapInstance)\n        setIsOriginMarkerMounted(true)\n      } else {\n        if (mapInstance.getSource(originMarkerSourceId)) {\n          return\n        }\n\n        mapInstance.addSource(originMarkerSourceId, {\n          type: \"geojson\",\n          data: {\n            type: \"Feature\",\n            properties: {},\n            geometry: { type: \"Point\", coordinates: originRef.current },\n          },\n        })\n\n        mapInstance.addLayer({\n          id: originMarkerLayerId,\n          type: \"circle\",\n          source: originMarkerSourceId,\n          paint: {\n            \"circle-radius\": ORIGIN_MARKER_RADIUS,\n            \"circle-color\": originMarkerColor,\n            \"circle-stroke-width\": 3,\n            \"circle-stroke-color\": \"#ffffff\",\n          },\n        })\n\n        if (originMarkerPulse) {\n          mapInstance.addSource(originPulseSourceId, {\n            type: \"geojson\",\n            data: {\n              type: \"Feature\",\n              properties: {},\n              geometry: { type: \"Point\", coordinates: originRef.current },\n            },\n          })\n\n          mapInstance.addLayer(\n            {\n              id: originPulseLayerId,\n              type: \"circle\",\n              source: originPulseSourceId,\n              paint: {\n                \"circle-radius\": PULSE_RADIUS,\n                \"circle-color\": originMarkerColor,\n                \"circle-opacity\": 0.3,\n              },\n            },\n            originMarkerLayerId\n          )\n        }\n      }\n    }\n\n    const addDestinationMarkers = (mapInstance: mapboxgl.Map) => {\n      if (!showDestinationMarkers) {\n        return\n      }\n\n      if (destinationMarkerIcon) {\n        destinationsRef.current.forEach((destination, index) => {\n          if (htmlDestinationMarkersRef.current[index]) {\n            return\n          }\n\n          const coordinates = getDestinationCoordinates(destination)\n          const markerElement = document.createElement(\"div\")\n          markerElement.style.width = `${DESTINATION_MARKER_SIZE}px`\n          markerElement.style.height = `${DESTINATION_MARKER_SIZE}px`\n          markerElement.style.borderRadius = \"50%\"\n          markerElement.style.backgroundColor = destinationMarkerColor || color\n          markerElement.style.display = \"flex\"\n          markerElement.style.alignItems = \"center\"\n          markerElement.style.justifyContent = \"center\"\n          markerElement.style.boxShadow = \"0 0 0 2px white\"\n          markerElement.style.opacity = \"0\"\n\n          destinationMarkerElementsRef.current[index] = markerElement\n          htmlDestinationMarkersRef.current[index] = new mapgl.Marker(markerElement)\n            .setLngLat(coordinates)\n            .addTo(mapInstance)\n        })\n        setDestinationMarkersMounted(new Array(destinationsRef.current.length).fill(true))\n      } else {\n        if (mapInstance.getSource(destinationMarkersSourceId)) {\n          return\n        }\n\n        mapInstance.addSource(destinationMarkersSourceId, {\n          type: \"geojson\",\n          data: {\n            type: \"FeatureCollection\",\n            features: [],\n          },\n        })\n\n        mapInstance.addLayer({\n          id: destinationMarkersLayerId,\n          type: \"circle\",\n          source: destinationMarkersSourceId,\n          paint: {\n            \"circle-radius\": DESTINATION_MARKER_RADIUS,\n            \"circle-color\": [\"coalesce\", [\"get\", \"color\"], destinationMarkerColor || color],\n            \"circle-stroke-width\": 2,\n            \"circle-stroke-color\": \"#ffffff\",\n          },\n        })\n      }\n    }\n\n    const addTravelingMarker = (mapInstance: mapboxgl.Map) => {\n      if (!showTravelingMarker) {\n        return\n      }\n\n      if (travelingMarkerIcon) {\n        if (htmlTravelingMarkerRef.current) {\n          return\n        }\n\n        const markerElement = document.createElement(\"div\")\n        markerElement.style.width = `${DESTINATION_MARKER_SIZE}px`\n        markerElement.style.height = `${DESTINATION_MARKER_SIZE}px`\n        markerElement.style.borderRadius = \"50%\"\n        markerElement.style.backgroundColor = travelingMarkerColor || color\n        markerElement.style.display = \"flex\"\n        markerElement.style.alignItems = \"center\"\n        markerElement.style.justifyContent = \"center\"\n\n        travelingMarkerElementRef.current = markerElement\n        htmlTravelingMarkerRef.current = new mapgl.Marker(markerElement).setLngLat(originRef.current).addTo(mapInstance)\n        setIsTravelingMarkerMounted(true)\n      } else {\n        if (mapInstance.getSource(travelingMarkerSourceId)) {\n          return\n        }\n\n        mapInstance.addSource(travelingMarkerSourceId, {\n          type: \"geojson\",\n          data: {\n            type: \"Feature\",\n            properties: {},\n            geometry: { type: \"Point\", coordinates: originRef.current },\n          },\n        })\n\n        mapInstance.addLayer({\n          id: travelingMarkerLayerId,\n          type: \"circle\",\n          source: travelingMarkerSourceId,\n          paint: {\n            \"circle-radius\": TRAVELING_MARKER_RADIUS,\n            \"circle-color\": travelingMarkerColor || color,\n            \"circle-stroke-width\": 2,\n            \"circle-stroke-color\": \"#ffffff\",\n          },\n        })\n      }\n    }\n\n    const addSources = (mapInstance: mapboxgl.Map) => {\n      try {\n        addLinesSource(mapInstance)\n        addOriginMarker(mapInstance)\n        addDestinationMarkers(mapInstance)\n        addTravelingMarker(mapInstance)\n        initializedRef.current = true\n      } catch (error) {\n        console.error(\"Error adding radial lines:\", error)\n      }\n    }\n\n    const cleanupResources = (mapInstance: mapboxgl.Map) => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current)\n      }\n\n      if (htmlOriginMarkerRef.current) {\n        htmlOriginMarkerRef.current.remove()\n        htmlOriginMarkerRef.current = null\n      }\n\n      htmlDestinationMarkersRef.current.forEach((marker) => {\n        marker?.remove()\n      })\n      htmlDestinationMarkersRef.current = []\n      destinationMarkerElementsRef.current = []\n\n      if (htmlTravelingMarkerRef.current) {\n        htmlTravelingMarkerRef.current.remove()\n        htmlTravelingMarkerRef.current = null\n      }\n\n      try {\n        if (mapInstance.getLayer(linesLayerId)) {\n          mapInstance.removeLayer(linesLayerId)\n        }\n        if (mapInstance.getLayer(originMarkerLayerId)) {\n          mapInstance.removeLayer(originMarkerLayerId)\n        }\n        if (mapInstance.getLayer(originPulseLayerId)) {\n          mapInstance.removeLayer(originPulseLayerId)\n        }\n        if (mapInstance.getLayer(destinationMarkersLayerId)) {\n          mapInstance.removeLayer(destinationMarkersLayerId)\n        }\n        if (mapInstance.getLayer(travelingMarkerLayerId)) {\n          mapInstance.removeLayer(travelingMarkerLayerId)\n        }\n        if (mapInstance.getSource(linesSourceId)) {\n          mapInstance.removeSource(linesSourceId)\n        }\n        if (mapInstance.getSource(originMarkerSourceId)) {\n          mapInstance.removeSource(originMarkerSourceId)\n        }\n        if (mapInstance.getSource(originPulseSourceId)) {\n          mapInstance.removeSource(originPulseSourceId)\n        }\n        if (mapInstance.getSource(destinationMarkersSourceId)) {\n          mapInstance.removeSource(destinationMarkersSourceId)\n        }\n        if (mapInstance.getSource(travelingMarkerSourceId)) {\n          mapInstance.removeSource(travelingMarkerSourceId)\n        }\n      } catch {\n        // Resources may already be removed\n      }\n\n      originMarkerElementRef.current = null\n      travelingMarkerElementRef.current = null\n      setIsOriginMarkerMounted(false)\n      setIsTravelingMarkerMounted(false)\n      setDestinationMarkersMounted([])\n      initializedRef.current = false\n    }\n\n    const handleStyleLoad = () => {\n      styleLoadedRef.current = true\n      initializedRef.current = false\n      addSources(map)\n\n      if (autoStart) {\n        setIsAnimating(true)\n      }\n    }\n\n    const handleStyleDataLoading = () => {\n      styleLoadedRef.current = false\n    }\n\n    if (isLoaded && !initializedRef.current) {\n      addSources(map)\n      styleLoadedRef.current = true\n    }\n\n    map.on(\"style.load\", handleStyleLoad)\n    map.on(\"styledataloading\", handleStyleDataLoading)\n\n    return () => {\n      map.off(\"style.load\", handleStyleLoad)\n      map.off(\"styledataloading\", handleStyleDataLoading)\n      cleanupResources(map)\n    }\n  }, [map, isLoaded, id])\n\n  useEffect(() => {\n    if (!map || !initializedRef.current) {\n      return\n    }\n\n    try {\n      if (map.getLayer(linesLayerId)) {\n        map.setPaintProperty(linesLayerId, \"line-width\", width)\n      }\n    } catch {\n      // Layer may not exist during style transition\n    }\n  }, [map, id, width])\n\n  useEffect(() => {\n    if (!map || !initializedRef.current) {\n      return\n    }\n\n    try {\n      if (map.getLayer(linesLayerId)) {\n        map.setPaintProperty(linesLayerId, \"line-opacity\", opacity)\n      }\n    } catch {\n      // Layer may not exist during style transition\n    }\n  }, [map, id, opacity])\n\n  useEffect(() => {\n    if (!map || !isLoaded || !initializedRef.current) {\n      return\n    }\n    if (!autoStart && !isAnimating) {\n      return\n    }\n\n    startTimeRef.current = Date.now()\n    lineProgressRef.current = new Array(destinationsRef.current.length).fill(0)\n    const completedLinesRef = { current: new Set<number>() }\n\n    const curvedPaths = destinationsRef.current.map((destination) => {\n      const coordinates = getDestinationCoordinates(destination)\n      const lineCurvature = calculateCurvature(originRef.current, coordinates, curvatureRef.current)\n      return generateCurvedPath(originRef.current, coordinates, lineCurvature, curveSegmentsRef.current)\n    })\n\n    const totalDuration = durationRef.current + staggerDelayRef.current * (destinationsRef.current.length - 1)\n\n    const updateLinesSource = (features: GeoJSON.Feature[]) => {\n      if (!map || !map.getStyle()) {\n        return\n      }\n      try {\n        const linesSource = map.getSource(linesSourceId) as mapboxgl.GeoJSONSource\n        if (linesSource) {\n          linesSource.setData({\n            type: \"FeatureCollection\",\n            features,\n          })\n        }\n      } catch {\n        // Map may be in an invalid state\n      }\n    }\n\n    const updateDestinationMarkersSource = (completedIndices: number[]) => {\n      if (destinationMarkerIconRef.current) {\n        completedIndices.forEach((index) => {\n          const markerElement = destinationMarkerElementsRef.current[index]\n          if (markerElement) {\n            markerElement.style.opacity = \"1\"\n          }\n        })\n        return\n      }\n\n      if (!map || !map.getStyle()) {\n        return\n      }\n      try {\n        const markersSource = map.getSource(destinationMarkersSourceId) as mapboxgl.GeoJSONSource\n        if (markersSource) {\n          const features = completedIndices.map((index) => {\n            const destination = destinationsRef.current[index]\n            const coordinates = getDestinationCoordinates(destination)\n            const destinationColor = getDestinationColor(destination)\n            return {\n              type: \"Feature\" as const,\n              properties: { color: destinationColor },\n              geometry: { type: \"Point\" as const, coordinates: coordinates },\n            }\n          })\n          markersSource.setData({\n            type: \"FeatureCollection\",\n            features,\n          })\n        }\n      } catch {\n        // Map may be in an invalid state\n      }\n    }\n\n    const updateTravelingMarkerPosition = (position: MapCoordinates) => {\n      if (travelingMarkerIconRef.current && htmlTravelingMarkerRef.current) {\n        htmlTravelingMarkerRef.current.setLngLat(position)\n        return\n      }\n\n      if (!map || !map.getStyle()) {\n        return\n      }\n      try {\n        const markerSource = map.getSource(travelingMarkerSourceId) as mapboxgl.GeoJSONSource\n        if (markerSource) {\n          markerSource.setData({\n            type: \"Feature\",\n            properties: {},\n            geometry: { type: \"Point\", coordinates: position },\n          })\n        }\n      } catch {\n        // Map may be in an invalid state\n      }\n    }\n\n    const stopAnimation = () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current)\n        animationFrameRef.current = undefined\n      }\n      setIsAnimating(false)\n    }\n\n    const animate = () => {\n      if (!map || !styleLoadedRef.current) {\n        stopAnimation()\n        return\n      }\n\n      const elapsed = Date.now() - startTimeRef.current\n      const overallProgress = Math.min(elapsed / totalDuration, 1)\n\n      const features: GeoJSON.Feature[] = []\n      let activeLineIndex = -1\n      let activeLineProgress = 0\n\n      destinationsRef.current.forEach((destination, index) => {\n        const lineStartTime = index * staggerDelayRef.current\n        const lineElapsed = Math.max(0, elapsed - lineStartTime)\n        const lineProgress = Math.min(lineElapsed / durationRef.current, 1)\n\n        lineProgressRef.current[index] = lineProgress\n\n        if (lineProgress > 0) {\n          const path = curvedPaths[index]\n          const pointCount = Math.floor(lineProgress * (path.length - 1)) + 1\n          const visiblePath = path.slice(0, pointCount)\n\n          const segmentProgress = (lineProgress * (path.length - 1)) % 1\n          if (pointCount < path.length && segmentProgress > 0) {\n            const startPoint = path[pointCount - 1]\n            const endPoint = path[pointCount]\n            visiblePath.push([\n              startPoint[0] + (endPoint[0] - startPoint[0]) * segmentProgress,\n              startPoint[1] + (endPoint[1] - startPoint[1]) * segmentProgress,\n            ])\n          }\n\n          const destinationColor = getDestinationColor(destination)\n          features.push({\n            type: \"Feature\",\n            properties: { color: destinationColor },\n            geometry: { type: \"LineString\", coordinates: visiblePath },\n          })\n\n          if (lineProgress < 1) {\n            activeLineIndex = index\n            activeLineProgress = lineProgress\n          }\n\n          if (lineProgress >= 1 && !completedLinesRef.current.has(index)) {\n            completedLinesRef.current.add(index)\n            const coordinates = getDestinationCoordinates(destination)\n            onLineCompleteRef.current?.(index, coordinates)\n          }\n        }\n      })\n\n      try {\n        updateLinesSource(features)\n        updateDestinationMarkersSource(Array.from(completedLinesRef.current))\n\n        if (showTravelingMarkerRef.current && activeLineIndex >= 0) {\n          const path = curvedPaths[activeLineIndex]\n          const pointIndex = Math.floor(activeLineProgress * (path.length - 1))\n          const segmentProgress = (activeLineProgress * (path.length - 1)) % 1\n          const startPoint = path[pointIndex]\n          const endPoint = path[Math.min(pointIndex + 1, path.length - 1)]\n          const position: MapCoordinates = [\n            startPoint[0] + (endPoint[0] - startPoint[0]) * segmentProgress,\n            startPoint[1] + (endPoint[1] - startPoint[1]) * segmentProgress,\n          ]\n          updateTravelingMarkerPosition(position)\n        }\n      } catch {\n        stopAnimation()\n        return\n      }\n\n      if (overallProgress < 1) {\n        animationFrameRef.current = requestAnimationFrame(animate)\n        return\n      }\n\n      setIsAnimating(false)\n      onCompleteRef.current?.()\n\n      if (loop) {\n        loopTimeoutRef.current = setTimeout(() => {\n          completedLinesRef.current.clear()\n          destinationMarkerElementsRef.current.forEach((markerElement) => {\n            if (markerElement) {\n              markerElement.style.opacity = \"0\"\n            }\n          })\n          updateLinesSource([])\n          updateDestinationMarkersSource([])\n          setIsAnimating(true)\n        }, loopDelay)\n      }\n    }\n\n    setIsAnimating(true)\n    animationFrameRef.current = requestAnimationFrame(animate)\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current)\n      }\n      if (loopTimeoutRef.current) {\n        clearTimeout(loopTimeoutRef.current)\n      }\n    }\n  }, [map, isLoaded, id, autoStart, loop, loopDelay, isAnimating])\n\n  const renderOriginMarkerIcon = () => {\n    if (originMarkerIcon && isOriginMarkerMounted && originMarkerElementRef.current) {\n      return createPortal(originMarkerIcon, originMarkerElementRef.current)\n    }\n    return null\n  }\n\n  const renderTravelingMarkerIcon = () => {\n    if (travelingMarkerIcon && isTravelingMarkerMounted && travelingMarkerElementRef.current) {\n      return createPortal(travelingMarkerIcon, travelingMarkerElementRef.current)\n    }\n    return null\n  }\n\n  const renderDestinationMarkerIcons = () => {\n    if (!destinationMarkerIcon || destinationMarkersMounted.length === 0) {\n      return null\n    }\n\n    return destinationMarkerElementsRef.current.map((markerElement, index) => {\n      if (markerElement && destinationMarkersMounted[index]) {\n        return createPortal(destinationMarkerIcon, markerElement, `dest-icon-${index}`)\n      }\n      return null\n    })\n  }\n\n  return (\n    <>\n      {renderOriginMarkerIcon()}\n      {renderTravelingMarkerIcon()}\n      {renderDestinationMarkerIcons()}\n    </>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/ui/map/line-radial.tsx"
    }
  ],
  "type": "registry:ui"
}
