{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tactical-map",
  "type": "registry:ui",
  "title": "Tactical map",
  "description": "SVG-based tactical map canvas with markers, zones and integrity bar.",
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "utils",
    "status-badge",
    "panel"
  ],
  "files": [
    {
      "path": "registry/blacksite/maps/tactical-map.tsx",
      "type": "registry:ui",
      "target": "components/maps/tactical-map.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { MapPin, Plus, Maximize2, Minus } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { StatusBadge, type StatusBadgeProps } from \"@/registry/blacksite/ui/status-badge\";\n\nexport interface MapMarker {\n  id: string;\n  label?: string;\n  /** Position in 0..1 normalised coordinates. */\n  x: number;\n  y: number;\n  tone?: \"primary\" | \"success\" | \"warning\" | \"danger\" | \"info\" | \"gold\" | \"neutral\";\n  status?: StatusBadgeProps[\"status\"];\n  /** Render as a pin (default) or triangle. */\n  shape?: \"pin\" | \"triangle\" | \"square\";\n}\n\nexport interface MapZone {\n  id: string;\n  /** Polygon points in normalised 0..1 coordinates. */\n  points: Array<[number, number]>;\n  label?: string;\n  tone?: \"primary\" | \"warning\" | \"danger\" | \"info\" | \"gold\";\n  /** Style of the zone outline. */\n  variant?: \"dashed\" | \"solid\";\n}\n\ninterface TacticalMapProps extends React.HTMLAttributes<HTMLDivElement> {\n  markers?: MapMarker[];\n  zones?: MapZone[];\n  /**\n   * Aspect ratio, e.g. \"4 / 3\" or \"16 / 9\". Pass `null` (or omit) to let the\n   * map fill its parent — useful when the map is inside a flex container that\n   * already constrains height.\n   */\n  aspectRatio?: string | null;\n  /** Show the zoom controls overlay. */\n  controls?: boolean;\n  /** Background grid type. */\n  grid?: \"fine\" | \"coarse\" | \"none\";\n  /** Optional caption rendered top-left. */\n  caption?: React.ReactNode;\n}\n\nconst toneToColor: Record<NonNullable<MapMarker[\"tone\"]>, string> = {\n  primary: \"var(--color-primary)\",\n  success: \"var(--color-success)\",\n  warning: \"var(--color-warning)\",\n  danger: \"var(--color-danger)\",\n  info: \"var(--color-info)\",\n  gold: \"var(--color-gold)\",\n  neutral: \"var(--color-foreground-muted)\",\n};\n\nconst TacticalMap = React.forwardRef<HTMLDivElement, TacticalMapProps>(\n  (\n    {\n      className,\n      markers = [],\n      zones = [],\n      aspectRatio = \"4 / 3\",\n      controls = true,\n      grid = \"fine\",\n      caption,\n      ...props\n    },\n    ref,\n  ) => {\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"border-border bg-background-overlay relative w-full overflow-hidden rounded-md border\",\n          grid === \"fine\" && \"bg-grid-fine\",\n          grid === \"coarse\" && \"bg-grid\",\n          className,\n        )}\n        style={aspectRatio ? { aspectRatio } : undefined}\n        {...props}\n      >\n        {/* Subtle radial gradient lighting */}\n        <div\n          aria-hidden\n          className=\"pointer-events-none absolute inset-0\"\n          style={{\n            background:\n              \"radial-gradient(ellipse at 30% 30%, hsl(var(--primary) / 0.18), transparent 65%), radial-gradient(ellipse at 70% 80%, hsl(var(--info) / 0.1), transparent 60%)\",\n          }}\n        />\n\n        <svg\n          viewBox=\"0 0 100 100\"\n          preserveAspectRatio=\"none\"\n          className=\"absolute inset-0 size-full\"\n        >\n          {zones.map((zone) => {\n            const pts = zone.points.map(([x, y]) => `${x * 100},${y * 100}`).join(\" \");\n            const color = toneToColor[zone.tone ?? \"warning\"];\n            return (\n              <g key={zone.id}>\n                <polygon\n                  points={pts}\n                  fill={color}\n                  fillOpacity={0.08}\n                  stroke={color}\n                  strokeOpacity={0.7}\n                  strokeWidth={0.3}\n                  strokeDasharray={zone.variant === \"solid\" ? undefined : \"1.2 1.2\"}\n                  vectorEffect=\"non-scaling-stroke\"\n                />\n              </g>\n            );\n          })}\n        </svg>\n\n        {/* Zone labels in normal DOM (so we can use real fonts) */}\n        {zones.map((zone) => {\n          const cx = zone.points.reduce((s, p) => s + p[0], 0) / zone.points.length;\n          const cy = zone.points.reduce((s, p) => s + p[1], 0) / zone.points.length;\n          if (!zone.label) return null;\n          const color = toneToColor[zone.tone ?? \"warning\"];\n          return (\n            <div\n              key={`${zone.id}-label`}\n              className=\"absolute -translate-x-1/2 -translate-y-1/2\"\n              style={{ left: `${cx * 100}%`, top: `${cy * 100}%` }}\n            >\n              <span\n                className=\"text-mono bg-background/80 rounded-[2px] border px-1 py-0.5 text-[10px] tracking-[0.1em] uppercase\"\n                style={{ color, borderColor: color }}\n              >\n                {zone.label}\n              </span>\n            </div>\n          );\n        })}\n\n        {/* Markers */}\n        {markers.map((m) => (\n          <Marker key={m.id} marker={m} />\n        ))}\n\n        {/* Caption */}\n        {caption && (\n          <div className=\"text-mono text-foreground-muted bg-background/60 border-border absolute top-2 left-2 rounded-sm border px-1.5 py-0.5 text-[10px] tracking-[0.1em] uppercase backdrop-blur-sm\">\n            {caption}\n          </div>\n        )}\n\n        {/* Controls */}\n        {controls && (\n          <div className=\"absolute top-2 right-2 flex flex-col gap-1\">\n            <MapButton ariaLabel=\"Zoom in\">\n              <Plus className=\"size-3\" />\n            </MapButton>\n            <MapButton ariaLabel=\"Zoom out\">\n              <Minus className=\"size-3\" />\n            </MapButton>\n            <MapButton ariaLabel=\"Fit to bounds\">\n              <Maximize2 className=\"size-3\" />\n            </MapButton>\n          </div>\n        )}\n      </div>\n    );\n  },\n);\nTacticalMap.displayName = \"TacticalMap\";\n\nfunction MapButton({ children, ariaLabel }: { children: React.ReactNode; ariaLabel: string }) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={ariaLabel}\n      disabled\n      className=\"border-border bg-background/80 text-foreground-muted hover:text-foreground hover:border-border-strong grid size-6 place-items-center rounded-sm border backdrop-blur-sm disabled:cursor-not-allowed disabled:opacity-50\"\n    >\n      {children}\n    </button>\n  );\n}\n\nfunction Marker({ marker }: { marker: MapMarker }) {\n  const color = toneToColor[marker.tone ?? \"primary\"];\n  const shape = marker.shape ?? \"pin\";\n\n  return (\n    <div\n      className=\"absolute -translate-x-1/2 -translate-y-1/2\"\n      style={{ left: `${marker.x * 100}%`, top: `${marker.y * 100}%` }}\n    >\n      <div className=\"flex flex-col items-center gap-1\">\n        {shape === \"pin\" && (\n          <span\n            className=\"bg-background grid size-5 place-items-center rounded-full border-2\"\n            style={{ borderColor: color, color }}\n          >\n            <MapPin className=\"size-3\" />\n          </span>\n        )}\n        {shape === \"triangle\" && (\n          <span\n            className=\"size-0\"\n            style={{\n              borderLeft: \"8px solid transparent\",\n              borderRight: \"8px solid transparent\",\n              borderBottom: `12px solid ${color}`,\n            }}\n          />\n        )}\n        {shape === \"square\" && (\n          <span className=\"size-3 rotate-45 border-2\" style={{ borderColor: color }} />\n        )}\n        {marker.label && (\n          <span\n            className=\"text-mono bg-background/80 rounded-[2px] border px-1 py-0.5 text-[10px] tracking-[0.08em] whitespace-nowrap uppercase\"\n            style={{ color, borderColor: color }}\n          >\n            {marker.label}\n          </span>\n        )}\n        {marker.status && <StatusBadge status={marker.status} />}\n      </div>\n    </div>\n  );\n}\n\nexport { TacticalMap };\n"
    }
  ]
}
