{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "background-grid",
  "title": "Background Grid",
  "description": "A static 3D background grid - a glowing wireframe perspective grid that recedes into the dark, with depth from atmospheric falloff and light pooling at each intersection.",
  "files": [
    {
      "path": "components/backgrounds/BackgroundGrid.tsx",
      "content": "'use client';\n\nimport React, { useEffect, useRef, type ReactNode } from 'react';\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n//\n// children           Content layered above the grid (headline, CTA, etc.)\n// className          Styles for the content wrapper\n// containerClassName Styles for the outer shell (size, layout)\n// theme              \"dark\" (default) | \"light\"\n// backgroundFill     Overrides the theme's background color (hex)\n// lineColor          Overrides the theme's grid line color (hex)\n// cells              Grid density (lines per axis)\n//\ntype BackgroundGridProps = {\n  children?: ReactNode;\n  className?: string;\n  containerClassName?: string;\n  theme?: 'dark' | 'light';\n  backgroundFill?: string;\n  lineColor?: string;\n  cells?: number;\n};\n\n// ---------------------------------------------------------------------------\n// #rrggbb -> \"r, g, b\" so alpha can vary per segment\n// ---------------------------------------------------------------------------\nfunction toRgb(hex: string) {\n  const h = hex.replace('#', '');\n  const full =\n    h.length === 3\n      ? h\n        .split('')\n        .map((c) => c + c)\n        .join('')\n      : h;\n  const n = parseInt(full, 16);\n  return `${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}`;\n}\n\n// ---------------------------------------------------------------------------\n// Per-theme defaults.\n// ---------------------------------------------------------------------------\nconst THEMES = {\n  dark: {\n    background: '#02040c',\n    line: '#2563eb',\n  },\n  light: {\n    background: '#eef2ff',\n    line: '#1d4ed8',\n  },\n} as const;\n\nexport default function BackgroundGrid({\n  children,\n  className = '',\n  containerClassName = '',\n  theme = 'dark',\n  backgroundFill,\n  lineColor = '#7eeab4',\n  cells = 14,\n}: BackgroundGridProps) {\n  const tokens = THEMES[theme] ?? THEMES.dark;\n  const bg = backgroundFill ?? tokens.background;\n  const line = lineColor ?? tokens.line;\n\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    const rgb = toRgb(line);\n    let width = 0;\n    let height = 0;\n\n    // -------------------------------------------------------------------------\n    // Warp a flat (u, v) grid — both axes 0..1 — into a full-bleed wavy plane.\n    // A radial pincushion pull toward an off-frame vanishing point bows every\n    // line, while depth (distance from that point) drives fade + thickness.\n    // Because u and v both span 0..1 edge to edge, the canvas is fully covered\n    // — no fan, no dead wedge.\n    // -------------------------------------------------------------------------\n    const vpx = 1.05; // vanishing point sits just outside the top-right corner\n    const vpy = -0.08;\n    const reach = 1.7; // how far the pull's influence extends across the plane\n    const pad = 0.38;\n    const uMin = -pad;\n    const vMin = -pad;\n    const span = 1 + pad * 2;\n    const lineCount = Math.round(cells * span);\n\n    const project = (u: number, v: number) => {\n      const dx = u - vpx;\n      const dy = v - vpy;\n      const dist = Math.hypot(dx, dy);\n      const t = Math.max(0, Math.min(1, dist / reach));\n\n      // Compress points toward the vanishing point — strongest close in,\n      // tapering to none at the far edge. This is what bows straight lines\n      // into curves and packs them tight near the corner, like the reference.\n      const compress = 1 - t;\n      const wx = vpx + dx * (t + compress * compress * 1.15);\n      const wy = vpy + dy * (t + compress * compress * 1.15);\n\n      const depth = compress;\n      return { x: wx * width, y: wy * height, depth };\n    };\n\n    const draw = () => {\n      ctx.fillStyle = bg;\n      ctx.fillRect(0, 0, width, height);\n      ctx.lineCap = 'round';\n\n      const steps = 24; // samples per line, enough to render the curve smoothly\n\n      // Lines of constant u (verticals before the warp)\n      for (let i = 0; i <= lineCount; i++) {\n        const u = uMin + (i / lineCount) * span;\n        ctx.beginPath();\n        for (let s = 0; s <= steps; s++) {\n          const p = project(u, vMin + (s / steps) * span);\n          if (s === 0) ctx.moveTo(p.x, p.y);\n          else ctx.lineTo(p.x, p.y);\n        }\n        const mid = project(u, 0.5);\n        ctx.strokeStyle = `rgba(${rgb}, ${0.3 + mid.depth * 0.6})`;\n        ctx.lineWidth = 0.8 + mid.depth * 2.2;\n        ctx.stroke();\n      }\n\n      // Lines of constant v (horizontals before the warp)\n      for (let j = 0; j <= lineCount; j++) {\n        const v = vMin + (j / lineCount) * span;\n        ctx.beginPath();\n        for (let s = 0; s <= steps; s++) {\n          const p = project(uMin + (s / steps) * span, v);\n          if (s === 0) ctx.moveTo(p.x, p.y);\n          else ctx.lineTo(p.x, p.y);\n        }\n        const mid = project(0.5, v);\n        ctx.strokeStyle = `rgba(${rgb}, ${0.3 + mid.depth * 0.6})`;\n        ctx.lineWidth = 0.8 + mid.depth * 2.2;\n        ctx.stroke();\n      }\n    };\n\n    const resize = () => {\n      const rect = container.getBoundingClientRect();\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      width = Math.max(1, Math.floor(rect.width));\n      height = Math.max(1, Math.floor(rect.height));\n      canvas.width = Math.floor(width * dpr);\n      canvas.height = Math.floor(height * dpr);\n      canvas.style.width = `${width}px`;\n      canvas.style.height = `${height}px`;\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      draw();\n    };\n\n    resize();\n\n    const observer = new ResizeObserver(resize);\n    observer.observe(container);\n\n    return () => observer.disconnect();\n  }, [bg, line, cells]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={[\n        'relative flex min-h-[420px] w-full flex-col items-center justify-center overflow-hidden',\n        containerClassName,\n      ]\n        .filter(Boolean)\n        .join(' ')}\n      style={{ backgroundColor: bg }}\n    >\n      <canvas\n        ref={canvasRef}\n        className='absolute inset-0 z-0 h-full w-full'\n        aria-hidden\n      />\n      <div\n        className={['relative z-10 px-6 text-center', className]\n          .filter(Boolean)\n          .join(' ')}\n      >\n        {children}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/backgrounds/BackgroundGrid.tsx"
    }
  ],
  "categories": [
    "backgrounds"
  ],
  "type": "registry:component"
}