{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "globe",
  "title": "Globe",
  "author": "AlgorizeTech",
  "description": "An interactive 3D WebGL globe visualization with location markers, arc curves, and rotation controls. Use it for global presence and location showcases.",
  "dependencies": [],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/GlobeBlock.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type GlobeVariant =\n  | \"interactive-dots\"\n  | \"hologram-wireframe\"\n  | \"digital-matrix\"\n  | \"shield-energy\"\n  | \"minimal-flat-3d\"\n  | \"heat-map\"\n  | \"cyber-hex\"\n  | \"radar-sweep\"\n  | \"galaxy-spiral\";\n\nexport interface LocationMarker {\n  id: string;\n  name: string;\n  lat: number;\n  lng: number;\n  label?: string;\n  value?: string;\n  color?: string;\n}\n\nexport interface FlightArc {\n  from: { lat: number; lng: number; name: string };\n  to: { lat: number; lng: number; name: string };\n  color?: string;\n}\n\nexport interface GlobeProps {\n  variant?: GlobeVariant;\n  size?: number;\n  speed?: number;\n  interactive?: boolean;\n  glowColor?: string;\n  primaryColor?: string;\n  secondaryColor?: string;\n  markers?: LocationMarker[];\n  arcs?: FlightArc[];\n  className?: string;\n}\n\nconst DEFAULT_CITIES: LocationMarker[] = [\n  { id: \"nyc\", name: \"New York\", lat: 40.7128, lng: -74.006, label: \"NYC Hub\" },\n  { id: \"lon\", name: \"London\", lat: 51.5074, lng: -0.1278, label: \"EMEA Node\" },\n  { id: \"tok\", name: \"Tokyo\", lat: 35.6762, lng: 139.6503, label: \"Asia HQ\" },\n  { id: \"syd\", name: \"Sydney\", lat: -33.8688, lng: 151.2093, label: \"APAC Gateway\" },\n  { id: \"dxb\", name: \"Dubai\", lat: 25.2048, lng: 55.2708, label: \"MENA Relay\" },\n  { id: \"sao\", name: \"São Paulo\", lat: -23.5505, lng: -46.6333, label: \"LATAM Node\" },\n];\n\nconst DEFAULT_ARCS: FlightArc[] = [\n  { from: { lat: 40.7128, lng: -74.006, name: \"NYC\" }, to: { lat: 51.5074, lng: -0.1278, name: \"LON\" } },\n  { from: { lat: 51.5074, lng: -0.1278, name: \"LON\" }, to: { lat: 25.2048, lng: 55.2708, name: \"DXB\" } },\n  { from: { lat: 25.2048, lng: 55.2708, name: \"DXB\" }, to: { lat: 35.6762, lng: 139.6503, name: \"TOK\" } },\n  { from: { lat: 35.6762, lng: 139.6503, name: \"TOK\" }, to: { lat: -33.8688, lng: 151.2093, name: \"SYD\" } },\n  { from: { lat: 40.7128, lng: -74.006, name: \"NYC\" }, to: { lat: -23.5505, lng: -46.6333, name: \"SAO\" } },\n];\n\nfunction resolveCanvasColor(colorStr?: string, alpha: number = 1, element?: HTMLElement | null): string {\n  const fallback = `rgba(99, 102, 241, ${alpha})`;\n  if (!colorStr) return fallback;\n  let raw = colorStr.trim();\n\n  if (typeof window !== \"undefined\" && raw.includes(\"var(\")) {\n    const varName = raw.match(/var\\(([^,)]+)/)?.[1]?.trim();\n    if (varName) {\n      try {\n        const targetEl = element || document.documentElement;\n        const val = getComputedStyle(targetEl).getPropertyValue(varName).trim();\n        if (val) raw = val;\n      } catch {\n        // fallback\n      }\n    }\n  }\n\n  if (raw.startsWith(\"oklch\")) {\n    const inner = raw.match(/oklch\\(([^)]+)\\)/)?.[1]?.trim();\n    if (inner) {\n      const cleanInner = inner.split(\"/\")[0].trim();\n      return `oklch(${cleanInner} / ${alpha})`;\n    }\n  }\n\n  if (raw.startsWith(\"#\")) {\n    let hex = raw.substring(1);\n    if (hex.length === 3) {\n      hex = hex.split(\"\").map((c) => c + c).join(\"\");\n    }\n    if (hex.length === 6) {\n      const r = parseInt(hex.substring(0, 2), 16);\n      const g = parseInt(hex.substring(2, 4), 16);\n      const b = parseInt(hex.substring(4, 6), 16);\n      if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {\n        return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n      }\n    }\n  }\n\n  if (raw.startsWith(\"hsl\")) {\n    const match = raw.match(/hsla?\\(([\\d.]+)[,\\s]+([\\d.]+)%?[,\\s]+([\\d.]+)%?/);\n    if (match) {\n      return `hsla(${match[1]}, ${match[2]}%, ${match[3]}%, ${alpha})`;\n    }\n  }\n\n  if (raw.startsWith(\"rgb\")) {\n    const match = raw.match(/rgba?\\(([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n    if (match) {\n      return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${alpha})`;\n    }\n  }\n\n  const parts = raw.split(/[\\s,]+/);\n  if (parts.length >= 3 && !isNaN(parseFloat(parts[0]))) {\n    const h = parseFloat(parts[0]);\n    const s = parts[1].includes(\"%\") ? parts[1] : `${parts[1]}%`;\n    const l = parts[2].includes(\"%\") ? parts[2] : `${parts[2]}%`;\n    return `hsla(${h}, ${s}, ${l}, ${alpha})`;\n  }\n\n  return fallback;\n}\n\nfunction generateSpherePoints(count: number = 850) {\n  const points: { x: number; y: number; z: number; isLand: boolean; lat: number; lng: number }[] = [];\n  const phi = Math.PI * (3 - Math.sqrt(5));\n\n  for (let i = 0; i < count; i++) {\n    const y = 1 - (i / (count - 1)) * 2;\n    const radius = Math.sqrt(1 - y * y);\n    const theta = phi * i;\n\n    const x = Math.cos(theta) * radius;\n    const z = Math.sin(theta) * radius;\n\n    const lat = (Math.asin(y) * 180) / Math.PI;\n    const lng = (Math.atan2(z, x) * 180) / Math.PI;\n\n    const isLand = isApproxLand(lat, lng);\n    points.push({ x, y, z, isLand, lat, lng });\n  }\n  return points;\n}\n\nfunction isApproxLand(lat: number, lng: number): boolean {\n  if (lat > 15 && lat < 70 && lng > -160 && lng < -50) return true;\n  if (lat > -55 && lat < 12 && lng > -85 && lng < -35) return true;\n  if (lat > 35 && lat < 70 && lng > -10 && lng < 40) return true;\n  if (lat > -35 && lat < 35 && lng > -20 && lng < 50) return true;\n  if (lat > 5 && lat < 70 && lng > 60 && lng < 150) return true;\n  if (lat > -45 && lat < -10 && lng > 110 && lng < 155) return true;\n  return false;\n}\n\nfunction latLngToVector3(lat: number, lng: number, radius: number = 1) {\n  const phi = (90 - lat) * (Math.PI / 180);\n  const theta = (lng + 180) * (Math.PI / 180);\n\n  const x = -(radius * Math.sin(phi) * Math.cos(theta));\n  const z = radius * Math.sin(phi) * Math.sin(theta);\n  const y = radius * Math.cos(phi);\n\n  return { x, y, z };\n}\n\nexport function Globe({\n  variant = \"interactive-dots\",\n  size = 560,\n  speed = 1,\n  interactive = true,\n  glowColor = \"var(--primary)\",\n  primaryColor = \"var(--primary)\",\n  secondaryColor = \"var(--primary)\",\n  markers = DEFAULT_CITIES,\n  arcs = DEFAULT_ARCS,\n  className,\n}: GlobeProps) {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const [rotation, setRotation] = useState({ x: 0.2, y: 0 });\n  const [isDragging, setIsDragging] = useState(false);\n  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });\n\n  const handleMouseDown = (e: React.MouseEvent) => {\n    if (!interactive) return;\n    setIsDragging(true);\n    setDragStart({ x: e.clientX, y: e.clientY });\n  };\n\n  const handleMouseMove = (e: React.MouseEvent) => {\n    if (!isDragging || !interactive) return;\n    const deltaX = (e.clientX - dragStart.x) * 0.005;\n    const deltaY = (e.clientY - dragStart.y) * 0.005;\n\n    setRotation((prev) => ({\n      x: Math.max(-Math.PI / 2, Math.min(Math.PI / 2, prev.x + deltaY)),\n      y: prev.y + deltaX,\n    }));\n    setDragStart({ x: e.clientX, y: e.clientY });\n  };\n\n  const handleMouseUp = () => {\n    setIsDragging(false);\n  };\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    let animationFrameId: number;\n    let currentRotationY = rotation.y;\n    let currentRotationX = rotation.x;\n    const points = generateSpherePoints(variant === \"galaxy-spiral\" ? 1000 : 650);\n\n    const radius = (size / 2) * 0.85;\n    const centerX = size / 2;\n    const centerY = size / 2;\n\n    let time = 0;\n\n    const render = () => {\n      time += 0.02 * speed;\n      ctx.clearRect(0, 0, size, size);\n\n      if (!isDragging) {\n        currentRotationY += 0.004 * speed;\n      } else {\n        currentRotationY = rotation.y;\n        currentRotationX = rotation.x;\n      }\n\n      const cosX = Math.cos(currentRotationX);\n      const sinX = Math.sin(currentRotationX);\n      const cosY = Math.cos(currentRotationY);\n      const sinY = Math.sin(currentRotationY);\n\n      const project = (x: number, y: number, z: number) => {\n        const x1 = x * cosY - z * sinY;\n        const z1 = z * cosY + x * sinY;\n        const y2 = y * cosX - z1 * sinX;\n        const z2 = z1 * cosX + y * sinX;\n\n        return {\n          px: centerX + x1 * radius,\n          py: centerY - y2 * radius,\n          scale: (z2 + 1) / 2,\n          z: z2,\n        };\n      };\n\n      // Background theme glow aura\n      try {\n        const glowGradient = ctx.createRadialGradient(centerX, centerY, radius * 0.5, centerX, centerY, radius * 1.15);\n        glowGradient.addColorStop(0, resolveCanvasColor(glowColor, 0.3, canvas));\n        glowGradient.addColorStop(0.6, resolveCanvasColor(glowColor, 0.1, canvas));\n        glowGradient.addColorStop(1, \"transparent\");\n        ctx.fillStyle = glowGradient;\n        ctx.beginPath();\n        ctx.arc(centerX, centerY, radius * 1.15, 0, Math.PI * 2);\n        ctx.fill();\n      } catch {\n        // Fallback safely\n      }\n\n      // Variant Specific Surface & Decorative Elements\n      if (variant === \"hologram-wireframe\") {\n        ctx.strokeStyle = resolveCanvasColor(primaryColor, 0.3, canvas);\n        ctx.lineWidth = 1.2;\n\n        for (let i = 0; i < 8; i++) {\n          const angle = (i * Math.PI) / 8 + currentRotationY;\n          ctx.beginPath();\n          ctx.ellipse(centerX, centerY, radius * Math.abs(Math.cos(angle)), radius, 0, 0, Math.PI * 2);\n          ctx.stroke();\n        }\n\n        const scanY = centerY + Math.sin(time * 2) * radius * 0.8;\n        ctx.strokeStyle = resolveCanvasColor(primaryColor, 0.85, canvas);\n        ctx.lineWidth = 2.5;\n        ctx.beginPath();\n        ctx.moveTo(centerX - radius * 0.9, scanY);\n        ctx.lineTo(centerX + radius * 0.9, scanY);\n        ctx.stroke();\n      } else if (variant === \"shield-energy\") {\n        ctx.save();\n        ctx.strokeStyle = resolveCanvasColor(primaryColor, 0.5, canvas);\n        ctx.lineWidth = 2;\n        const shieldPulse = radius + Math.sin(time * 4) * 6;\n        ctx.beginPath();\n        ctx.arc(centerX, centerY, shieldPulse, 0, Math.PI * 2);\n        ctx.stroke();\n\n        const rippleR = (time * 50) % (radius * 1.1);\n        ctx.strokeStyle = resolveCanvasColor(secondaryColor, 0.6, canvas);\n        ctx.beginPath();\n        ctx.arc(centerX, centerY, rippleR, 0, Math.PI * 2);\n        ctx.stroke();\n        ctx.restore();\n      } else if (variant === \"heat-map\") {\n        for (let i = 0; i < 3; i++) {\n          const hRadius = radius * (0.3 + ((time * 0.5 + i * 0.3) % 0.7));\n          const opacity = Math.max(0, 1 - hRadius / radius);\n          ctx.strokeStyle = resolveCanvasColor(primaryColor, opacity * 0.8, canvas);\n          ctx.lineWidth = 3.5;\n          ctx.beginPath();\n          ctx.arc(centerX, centerY, hRadius, 0, Math.PI * 2);\n          ctx.stroke();\n        }\n      } else if (variant === \"radar-sweep\") {\n        ctx.strokeStyle = resolveCanvasColor(primaryColor, 0.25, canvas);\n        ctx.lineWidth = 1.2;\n        [0.3, 0.65, 0.95].forEach((rFactor) => {\n          ctx.beginPath();\n          ctx.arc(centerX, centerY, radius * rFactor, 0, Math.PI * 2);\n          ctx.stroke();\n        });\n\n        const sweepAngle = time * 2;\n        ctx.save();\n        ctx.translate(centerX, centerY);\n        ctx.rotate(sweepAngle);\n\n        const beamGrad = ctx.createConicGradient(0, 0, 0);\n        beamGrad.addColorStop(0, resolveCanvasColor(primaryColor, 0.35, canvas));\n        beamGrad.addColorStop(0.15, \"transparent\");\n        beamGrad.addColorStop(1, \"transparent\");\n\n        ctx.fillStyle = beamGrad;\n        ctx.beginPath();\n        ctx.arc(0, 0, radius * 1.05, 0, Math.PI * 2);\n        ctx.fill();\n        ctx.restore();\n      }\n\n      // Draw Sphere Points matching website theme\n      points.forEach((pt) => {\n        const proj = project(pt.x, pt.y, pt.z);\n        if (proj.z <= -0.1) return;\n\n        const depthAlpha = Math.pow(proj.scale, 1.4);\n        const ptRadius = (pt.isLand ? 2.6 : 1.4) * proj.scale;\n\n        ctx.beginPath();\n\n        if (variant === \"digital-matrix\") {\n          ctx.fillStyle = pt.isLand\n            ? resolveCanvasColor(primaryColor, depthAlpha, canvas)\n            : resolveCanvasColor(primaryColor, depthAlpha * 0.35, canvas);\n          ctx.font = `${Math.floor(9 * proj.scale)}px monospace`;\n          const char = (Math.floor(pt.lat + pt.lng + time * 10) % 2).toString();\n          ctx.fillText(char, proj.px, proj.py);\n        } else if (variant === \"galaxy-spiral\") {\n          const spiralOffset = Math.sin(time * 2 + pt.lat * 0.1) * 6;\n          ctx.fillStyle = pt.isLand\n            ? resolveCanvasColor(primaryColor, depthAlpha * 0.9, canvas)\n            : resolveCanvasColor(primaryColor, depthAlpha * 0.4, canvas);\n          ctx.arc(proj.px + spiralOffset, proj.py, Math.max(0.6, ptRadius * 1.2), 0, Math.PI * 2);\n          ctx.fill();\n        } else if (variant === \"cyber-hex\") {\n          ctx.fillStyle = pt.isLand\n            ? resolveCanvasColor(primaryColor, depthAlpha * 0.95, canvas)\n            : resolveCanvasColor(secondaryColor, depthAlpha * 0.25, canvas);\n          ctx.rect(proj.px - ptRadius, proj.py - ptRadius, ptRadius * 2, ptRadius * 2);\n          ctx.fill();\n        } else {\n          ctx.arc(proj.px, proj.py, Math.max(0.6, ptRadius), 0, Math.PI * 2);\n          if (pt.isLand) {\n            ctx.fillStyle = resolveCanvasColor(primaryColor, depthAlpha * 0.95, canvas);\n          } else {\n            ctx.fillStyle = resolveCanvasColor(secondaryColor, depthAlpha * 0.35, canvas);\n          }\n          ctx.fill();\n        }\n      });\n\n      // Render Arcs (Interactive Dots connection arcs)\n      if (variant === \"interactive-dots\") {\n        arcs.forEach((arc) => {\n          const fromVec = latLngToVector3(arc.from.lat, arc.from.lng);\n          const toVec = latLngToVector3(arc.to.lat, arc.to.lng);\n\n          const p1 = project(fromVec.x, fromVec.y, fromVec.z);\n          const p2 = project(toVec.x, toVec.y, toVec.z);\n\n          if (p1.z > 0 || p2.z > 0) {\n            const midX = (p1.px + p2.px) / 2;\n            const midY = (p1.py + p2.py) / 2 - radius * 0.38;\n\n            ctx.beginPath();\n            ctx.moveTo(p1.px, p1.py);\n            ctx.quadraticCurveTo(midX, midY, p2.px, p2.py);\n            ctx.strokeStyle = resolveCanvasColor(arc.color || primaryColor, 0.8, canvas);\n            ctx.lineWidth = 2.2;\n            ctx.setLineDash([5, 5]);\n            ctx.stroke();\n            ctx.setLineDash([]);\n\n            const progress = (time * 0.8) % 1;\n            const travelX = (1 - progress) * (1 - progress) * p1.px + 2 * (1 - progress) * progress * midX + progress * progress * p2.px;\n            const travelY = (1 - progress) * (1 - progress) * p1.py + 2 * (1 - progress) * progress * midY + progress * progress * p2.py;\n\n            ctx.fillStyle = \"#ffffff\";\n            ctx.beginPath();\n            ctx.arc(travelX, travelY, 3.5, 0, Math.PI * 2);\n            ctx.fill();\n\n            ctx.fillStyle = resolveCanvasColor(arc.color || primaryColor, 0.6, canvas);\n            ctx.beginPath();\n            ctx.arc(travelX, travelY, 7, 0, Math.PI * 2);\n            ctx.fill();\n          }\n        });\n      }\n\n      // Render Hotspot Markers using theme color\n      markers.forEach((marker) => {\n        const vec = latLngToVector3(marker.lat, marker.lng);\n        const proj = project(vec.x, vec.y, vec.z);\n\n        if (proj.z > 0.05) {\n          const markerColor = resolveCanvasColor(marker.color || primaryColor, 1, canvas);\n          const pulse = (time * 3) % 1;\n\n          ctx.strokeStyle = resolveCanvasColor(marker.color || primaryColor, (1 - pulse) * proj.scale, canvas);\n          ctx.lineWidth = 2.5;\n          ctx.beginPath();\n          ctx.arc(proj.px, proj.py, 5 + pulse * 16, 0, Math.PI * 2);\n          ctx.stroke();\n\n          ctx.fillStyle = markerColor;\n          ctx.beginPath();\n          ctx.arc(proj.px, proj.py, 5 * proj.scale, 0, Math.PI * 2);\n          ctx.fill();\n        }\n      });\n\n      animationFrameId = requestAnimationFrame(render);\n    };\n\n    render();\n\n    return () => {\n      cancelAnimationFrame(animationFrameId);\n    };\n  }, [variant, size, speed, rotation, isDragging, primaryColor, secondaryColor, glowColor, markers, arcs]);\n\n  return (\n    <div\n      className={cn(\n        \"relative flex items-center justify-center select-none overflow-hidden rounded-3xl p-2\",\n        className\n      )}\n      style={{ width: size, height: size }}\n    >\n      <div\n        className=\"relative cursor-grab active:cursor-grabbing flex items-center justify-center m-auto\"\n        onMouseDown={handleMouseDown}\n        onMouseMove={handleMouseMove}\n        onMouseUp={handleMouseUp}\n        onMouseLeave={handleMouseUp}\n      >\n        <canvas ref={canvasRef} width={size} height={size} className=\"touch-none\" />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/GlobeBlock.tsx"
    }
  ],
  "categories": [
    "data-display",
    "marketing"
  ],
  "type": "registry:component"
}
