{
  "name": "expanded-map",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "ui/expanded-map.tsx",
      "content": "\"use client\";\n\nimport {\n  AnimatePresence,\n  motion,\n  useMotionValue,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\nimport NextImage from \"next/image\";\nimport type React from \"react\";\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\ninterface LocationMapProps {\n  /** Location name to display */\n  location?: string;\n  /** Latitude coordinate */\n  latitude?: number;\n  /** Longitude coordinate */\n  longitude?: number;\n  /** Zoom level for the map (1-18) */\n  zoom?: number;\n  /** Additional CSS classes */\n  className?: string;\n  /** Map tile provider */\n  tileProvider?: \"openstreetmap\" | \"carto-light\" | \"carto-dark\";\n}\n\n// Convert lat/lng to tile coordinates\nfunction latLngToTile(lat: number, lng: number, zoom: number) {\n  const n = 2 ** zoom;\n  const x = Math.floor(((lng + 180) / 360) * n);\n  const latRad = (lat * Math.PI) / 180;\n  const y = Math.floor(\n    ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n,\n  );\n  return { x, y };\n}\n\n// Get tile URL based on provider\nfunction getTileUrl(provider: string, x: number, y: number, z: number) {\n  switch (provider) {\n    case \"carto-light\":\n      return `https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/${z}/${x}/${y}.png`;\n    case \"carto-dark\":\n      return `https://cartodb-basemaps-a.global.ssl.fastly.net/dark_all/${z}/${x}/${y}.png`;\n    default:\n      return `https://tile.openstreetmap.org/${z}/${x}/${y}.png`;\n  }\n}\n\n// Format coordinates for display\nfunction formatCoordinates(lat: number, lng: number) {\n  const latDir = lat >= 0 ? \"N\" : \"S\";\n  const lngDir = lng >= 0 ? \"E\" : \"W\";\n  return `${Math.abs(lat).toFixed(4)}° ${latDir}, ${Math.abs(lng).toFixed(4)}° ${lngDir}`;\n}\n\nexport function LocationMap({\n  location = \"San Francisco, CA\",\n  latitude = 37.7749,\n  longitude = -122.4194,\n  zoom = 14,\n  className,\n  tileProvider = \"carto-light\",\n}: LocationMapProps) {\n  const [isHovered, setIsHovered] = useState(false);\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [tilesLoaded, setTilesLoaded] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  const mouseX = useMotionValue(0);\n  const mouseY = useMotionValue(0);\n\n  const rotateX = useTransform(mouseY, [-50, 50], [8, -8]);\n  const rotateY = useTransform(mouseX, [-50, 50], [-8, 8]);\n\n  const springRotateX = useSpring(rotateX, { stiffness: 300, damping: 30 });\n  const springRotateY = useSpring(rotateY, { stiffness: 300, damping: 30 });\n\n  const coordinates = useMemo(\n    () => formatCoordinates(latitude, longitude),\n    [latitude, longitude],\n  );\n\n  // Generate tile URLs for a 3x3 grid around the center tile\n  const tiles = useMemo(() => {\n    const centerTile = latLngToTile(latitude, longitude, zoom);\n    const tileUrls: { url: string; offsetX: number; offsetY: number }[] = [];\n\n    for (let dy = -1; dy <= 1; dy++) {\n      for (let dx = -1; dx <= 1; dx++) {\n        tileUrls.push({\n          url: getTileUrl(\n            tileProvider,\n            centerTile.x + dx,\n            centerTile.y + dy,\n            zoom,\n          ),\n          offsetX: dx,\n          offsetY: dy,\n        });\n      }\n    }\n\n    return tileUrls;\n  }, [latitude, longitude, zoom, tileProvider]);\n\n  // Preload tiles\n  useEffect(() => {\n    let loadedCount = 0;\n    const totalTiles = tiles.length;\n\n    tiles.forEach((tile) => {\n      const img = new Image();\n      img.crossOrigin = \"anonymous\";\n      img.onload = () => {\n        loadedCount++;\n        if (loadedCount === totalTiles) {\n          setTilesLoaded(true);\n        }\n      };\n      img.onerror = () => {\n        loadedCount++;\n        if (loadedCount === totalTiles) {\n          setTilesLoaded(true);\n        }\n      };\n      img.src = tile.url;\n    });\n  }, [tiles]);\n\n  const handleMouseMove = (e: React.MouseEvent) => {\n    if (!containerRef.current) return;\n    const rect = containerRef.current.getBoundingClientRect();\n    const centerX = rect.left + rect.width / 2;\n    const centerY = rect.top + rect.height / 2;\n    mouseX.set(e.clientX - centerX);\n    mouseY.set(e.clientY - centerY);\n  };\n\n  const handleMouseLeave = () => {\n    mouseX.set(0);\n    mouseY.set(0);\n    setIsHovered(false);\n  };\n\n  const handleClick = () => {\n    setIsExpanded(!isExpanded);\n  };\n\n  return (\n    <motion.div\n      ref={containerRef}\n      className={`relative cursor-pointer select-none ${className}`}\n      style={{\n        perspective: 1000,\n      }}\n      onMouseMove={handleMouseMove}\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={handleMouseLeave}\n      onClick={handleClick}\n    >\n      <motion.div\n        className=\"relative overflow-hidden rounded-2xl border border-border bg-background\"\n        style={{\n          rotateX: springRotateX,\n          rotateY: springRotateY,\n          transformStyle: \"preserve-3d\",\n        }}\n        animate={{\n          width: isExpanded ? 360 : 240,\n          height: isExpanded ? 280 : 140,\n        }}\n        transition={{\n          type: \"spring\",\n          stiffness: 400,\n          damping: 35,\n        }}\n      >\n        {/* Subtle gradient overlay */}\n        <div className=\"pointer-events-none absolute inset-0 z-20 bg-gradient-to-br from-muted/20 via-transparent to-muted/40\" />\n\n        <AnimatePresence>\n          {isExpanded && (\n            <motion.div\n              className=\"pointer-events-none absolute inset-0\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={{ duration: 0.4, delay: 0.1 }}\n            >\n              {/* Real map tiles */}\n              <div className=\"absolute inset-0 overflow-hidden\">\n                <div\n                  className=\"absolute\"\n                  style={{\n                    width: \"768px\", // 3 tiles * 256px\n                    height: \"768px\",\n                    left: \"50%\",\n                    top: \"50%\",\n                    transform: \"translate(-50%, -50%)\",\n                  }}\n                >\n                  {tiles.map((tile, index) => (\n                    <motion.div\n                      key={index}\n                      className=\"absolute\"\n                      style={{\n                        width: \"256px\",\n                        height: \"256px\",\n                        left: `${(tile.offsetX + 1) * 256}px`,\n                        top: `${(tile.offsetY + 1) * 256}px`,\n                      }}\n                      initial={{ opacity: 0 }}\n                      animate={{ opacity: tilesLoaded ? 1 : 0 }}\n                      transition={{ duration: 0.3, delay: index * 0.05 }}\n                    >\n                      <NextImage\n                        src={tile.url}\n                        alt=\"\"\n                        width={256}\n                        height={256}\n                        unoptimized\n                        crossOrigin=\"anonymous\"\n                        className=\"h-full w-full\"\n                      />\n                    </motion.div>\n                  ))}\n                </div>\n              </div>\n\n              {/* Map loading placeholder */}\n              {!tilesLoaded && (\n                <div className=\"absolute inset-0 animate-pulse bg-muted\" />\n              )}\n\n              {/* Location marker */}\n              <motion.div\n                className=\"absolute top-1/2 left-1/2 z-10 -translate-x-1/2 -translate-y-1/2\"\n                initial={{ scale: 0, y: -20 }}\n                animate={{ scale: 1, y: 0 }}\n                transition={{\n                  type: \"spring\",\n                  stiffness: 400,\n                  damping: 20,\n                  delay: 0.3,\n                }}\n              >\n                <svg\n                  width=\"32\"\n                  height=\"32\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  className=\"drop-shadow-lg\"\n                  style={{\n                    filter: \"drop-shadow(0 0 10px rgba(52, 211, 153, 0.5))\",\n                  }}\n                >\n                  <path\n                    d=\"M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z\"\n                    fill=\"#34D399\"\n                  />\n                  <circle cx=\"12\" cy=\"9\" r=\"2.5\" className=\"fill-background\" />\n                </svg>\n              </motion.div>\n\n              {/* Gradient overlays for better text readability */}\n              <div className=\"absolute inset-0 z-10 bg-gradient-to-t from-background via-transparent to-transparent opacity-70\" />\n              <div className=\"absolute inset-0 z-10 bg-gradient-to-b from-background/50 via-transparent to-transparent\" />\n            </motion.div>\n          )}\n        </AnimatePresence>\n\n        {/* Grid pattern - only show when collapsed */}\n        <motion.div\n          className=\"absolute inset-0 opacity-[0.03]\"\n          animate={{ opacity: isExpanded ? 0 : 0.03 }}\n          transition={{ duration: 0.3 }}\n        >\n          <svg width=\"100%\" height=\"100%\" className=\"absolute inset-0\">\n            <defs>\n              <pattern\n                id=\"grid\"\n                width=\"20\"\n                height=\"20\"\n                patternUnits=\"userSpaceOnUse\"\n              >\n                <path\n                  d=\"M 20 0 L 0 0 0 20\"\n                  fill=\"none\"\n                  className=\"stroke-foreground\"\n                  strokeWidth=\"0.5\"\n                />\n              </pattern>\n            </defs>\n            <rect width=\"100%\" height=\"100%\" fill=\"url(#grid)\" />\n          </svg>\n        </motion.div>\n\n        {/* Content */}\n        <div className=\"relative z-20 flex h-full flex-col justify-between p-5\">\n          {/* Top section */}\n          <div className=\"flex items-start justify-between\">\n            <div className=\"relative\">\n              <motion.div\n                className=\"relative\"\n                animate={{\n                  opacity: isExpanded ? 0 : 1,\n                }}\n                transition={{ duration: 0.3 }}\n              >\n                {/* Map Icon SVG */}\n                <motion.svg\n                  width=\"18\"\n                  height=\"18\"\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"2\"\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  className=\"text-emerald-400\"\n                  animate={{\n                    filter: isHovered\n                      ? \"drop-shadow(0 0 8px rgba(52, 211, 153, 0.6))\"\n                      : \"drop-shadow(0 0 4px rgba(52, 211, 153, 0.3))\",\n                  }}\n                  transition={{ duration: 0.3 }}\n                >\n                  <polygon points=\"3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21\" />\n                  <line x1=\"9\" x2=\"9\" y1=\"3\" y2=\"18\" />\n                  <line x1=\"15\" x2=\"15\" y1=\"6\" y2=\"21\" />\n                </motion.svg>\n              </motion.div>\n            </div>\n          </div>\n\n          {/* Bottom section */}\n          <div className=\"space-y-1\">\n            <motion.h3\n              className=\"font-medium text-foreground text-sm tracking-tight\"\n              animate={{\n                x: isHovered ? 4 : 0,\n              }}\n              transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n            >\n              {location}\n            </motion.h3>\n\n            <AnimatePresence>\n              {isExpanded && (\n                <motion.p\n                  className=\"font-mono text-muted-foreground text-xs\"\n                  initial={{ opacity: 0, y: -10, height: 0 }}\n                  animate={{ opacity: 1, y: 0, height: \"auto\" }}\n                  exit={{ opacity: 0, y: -10, height: 0 }}\n                  transition={{ duration: 0.25 }}\n                >\n                  {coordinates}\n                </motion.p>\n              )}\n            </AnimatePresence>\n\n            {/* Animated underline */}\n            <motion.div\n              className=\"h-px bg-gradient-to-r from-emerald-500/50 via-emerald-400/30 to-transparent\"\n              initial={{ scaleX: 0, originX: 0 }}\n              animate={{\n                scaleX: isHovered || isExpanded ? 1 : 0.3,\n              }}\n              transition={{ duration: 0.4, ease: \"easeOut\" }}\n            />\n          </div>\n        </div>\n      </motion.div>\n    </motion.div>\n  );\n}\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}