{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "neon-circle-grid",
  "type": "registry:ui",
  "title": "Neon Circle Grid",
  "description": "A decorative SVG background of neon donut-ring circles with directional fade and colour blending.",
  "files": [
    {
      "path": "registry/ruixenui/neon-circle-grid.tsx",
      "content": "\"use client\";\n\nimport React, { useId } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface NeonCircleGridProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Outer radius of each donut */\n  outerRadius?: number;\n  /** Inner radius (the hole) */\n  innerRadius?: number;\n  /** Center-to-center spacing */\n  gap?: number;\n  /** Colour on the dark / entering side of the fade */\n  color?: string;\n  /** Colour on the bright / exiting side (defaults to warm off-white) */\n  colorBright?: string;\n  /** Which side is bright */\n  fadeFrom?: \"left\" | \"right\" | \"top-left\" | \"top-right\" | \"center\";\n  /** 0–1 — how much stays fully dark before fade begins */\n  fadeStart?: number;\n  /** 0–1 — where the bright colour starts blending in (relative to fade) */\n  colorBlend?: number;\n}\n\nexport function NeonCircleGrid({\n  className,\n  outerRadius = 9,\n  innerRadius = 3.5,\n  gap = 20,\n  color = \"#3ECF8E\",\n  colorBright = \"#D6E8D0\",\n  fadeFrom = \"right\",\n  fadeStart = 0,\n  colorBlend = 0.45,\n  ...props\n}: NeonCircleGridProps) {\n  const uid = useId().replace(/:/g, \"\");\n\n  const half = gap / 2;\n  const midR = (outerRadius + innerRadius) / 2;\n  const ringWidth = outerRadius - innerRadius;\n  const shadowA = darken(color, 0.5);\n  const shadowB = darken(colorBright, 0.3);\n\n  // fade‑mask gradient coords\n  const gradCoords: Record<\n    string,\n    { x1: string; y1: string; x2: string; y2: string }\n  > = {\n    left: { x1: \"1\", y1: \"0.5\", x2: \"0\", y2: \"0.5\" },\n    right: { x1: \"0\", y1: \"0.5\", x2: \"1\", y2: \"0.5\" },\n    \"top-left\": { x1: \"1\", y1: \"1\", x2: \"0\", y2: \"0\" },\n    \"top-right\": { x1: \"0\", y1: \"1\", x2: \"1\", y2: \"0\" },\n    center: { x1: \"0.5\", y1: \"0.5\", x2: \"1\", y2: \"1\" },\n  };\n  const gc = gradCoords[fadeFrom];\n  const isCenter = fadeFrom === \"center\";\n\n  /* helper: build a donut‑ring pattern tile */\n  const ringPattern = (\n    id: string,\n    strokeColor: string,\n    shadowColor: string,\n  ) => (\n    <pattern id={id} width={gap} height={gap} patternUnits=\"userSpaceOnUse\">\n      {/* Main ring — thick stroke = perfectly centred donut */}\n      <circle\n        cx={half}\n        cy={half}\n        r={midR}\n        fill=\"none\"\n        stroke={strokeColor}\n        strokeWidth={ringWidth}\n      />\n      {/* Outer rim — dark edge */}\n      <circle\n        cx={half}\n        cy={half}\n        r={outerRadius - 0.25}\n        fill=\"none\"\n        stroke={shadowColor}\n        strokeWidth={0.5}\n        opacity={0.45}\n      />\n      {/* Inner rim — dark edge around hole */}\n      <circle\n        cx={half}\n        cy={half}\n        r={innerRadius + 0.25}\n        fill=\"none\"\n        stroke={shadowColor}\n        strokeWidth={0.5}\n        opacity={0.45}\n      />\n      {/* Mid-band highlight for subtle depth */}\n      <circle\n        cx={half}\n        cy={half}\n        r={midR}\n        fill=\"none\"\n        stroke=\"white\"\n        strokeWidth={ringWidth * 0.25}\n        opacity={0.07}\n      />\n    </pattern>\n  );\n\n  return (\n    <div\n      className={cn(\n        \"pointer-events-none absolute inset-0 overflow-hidden\",\n        className,\n      )}\n      {...props}\n    >\n      <svg\n        aria-hidden=\"true\"\n        style={{\n          position: \"absolute\",\n          inset: \"-5%\",\n          width: \"110%\",\n          height: \"110%\",\n          animation: `_ncg-drift-${uid} 38s ease-in-out infinite`,\n        }}\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <style>{`\n          @keyframes _ncg-drift-${uid} {\n            0%   { transform: translate(0, 0) rotate(0deg); }\n            20%  { transform: translate(-8px, 5px) rotate(0.25deg); }\n            40%  { transform: translate(5px, -7px) rotate(-0.15deg); }\n            60%  { transform: translate(-5px, -8px) rotate(0.18deg); }\n            80%  { transform: translate(7px, 3px) rotate(-0.1deg); }\n            100% { transform: translate(0, 0) rotate(0deg); }\n          }\n          @keyframes _ncg-glow-${uid} {\n            0%, 100% { opacity: 0.22; }\n            50%      { opacity: 0.38; }\n          }\n        `}</style>\n        <defs>\n          {/* ── Two donut patterns: dark-side colour & bright-side colour ── */}\n          {ringPattern(`pat-a-${uid}`, color, shadowA)}\n          {ringPattern(`pat-b-${uid}`, colorBright, shadowB)}\n\n          {/* ── Mask A: full fade (controls overall visibility) ── */}\n          {isCenter ? (\n            <radialGradient id={`fade-a-${uid}`} cx=\"0.5\" cy=\"0.5\" r=\"0.7\">\n              <stop offset=\"0%\" stopColor=\"white\" stopOpacity={1} />\n              <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n            </radialGradient>\n          ) : (\n            <linearGradient\n              id={`fade-a-${uid}`}\n              x1={gc.x1}\n              y1={gc.y1}\n              x2={gc.x2}\n              y2={gc.y2}\n            >\n              <stop\n                offset={`${fadeStart * 100}%`}\n                stopColor=\"white\"\n                stopOpacity={0}\n              />\n              <stop offset=\"100%\" stopColor=\"white\" stopOpacity={1} />\n            </linearGradient>\n          )}\n          <mask id={`mask-a-${uid}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#fade-a-${uid})`} />\n          </mask>\n\n          {/* ── Mask B: bright-colour blend (kicks in later in the fade) ── */}\n          {isCenter ? (\n            <radialGradient id={`fade-b-${uid}`} cx=\"0.5\" cy=\"0.5\" r=\"0.5\">\n              <stop offset=\"0%\" stopColor=\"white\" stopOpacity={1} />\n              <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n            </radialGradient>\n          ) : (\n            <linearGradient\n              id={`fade-b-${uid}`}\n              x1={gc.x1}\n              y1={gc.y1}\n              x2={gc.x2}\n              y2={gc.y2}\n            >\n              <stop\n                offset={`${colorBlend * 100}%`}\n                stopColor=\"white\"\n                stopOpacity={0}\n              />\n              <stop offset=\"100%\" stopColor=\"white\" stopOpacity={1} />\n            </linearGradient>\n          )}\n          <mask id={`mask-b-${uid}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#fade-b-${uid})`} />\n          </mask>\n\n          {/* ── Soft glow filter ── */}\n          <filter\n            id={`glow-${uid}`}\n            x=\"-50%\"\n            y=\"-50%\"\n            width=\"200%\"\n            height=\"200%\"\n          >\n            <feGaussianBlur\n              in=\"SourceGraphic\"\n              stdDeviation=\"1.2\"\n              result=\"blur\"\n            />\n            <feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"over\" />\n          </filter>\n        </defs>\n\n        {/* Layer 1 — green circles (glow) */}\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#pat-a-${uid})`}\n          mask={`url(#mask-a-${uid})`}\n          filter={`url(#glow-${uid})`}\n          style={{ animation: `_ncg-glow-${uid} 7s ease-in-out infinite` }}\n        />\n        {/* Layer 2 — green circles (sharp) */}\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#pat-a-${uid})`}\n          mask={`url(#mask-a-${uid})`}\n        />\n        {/* Layer 3 — cream/bright circles on top (blends in on bright side) */}\n        <rect\n          width=\"100%\"\n          height=\"100%\"\n          fill={`url(#pat-b-${uid})`}\n          mask={`url(#mask-b-${uid})`}\n        />\n      </svg>\n    </div>\n  );\n}\n\n/* ── colour helpers ── */\n\nfunction hexToRgb(hex: string) {\n  const h = hex.replace(\"#\", \"\");\n  const n = parseInt(\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : h,\n    16,\n  );\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255] as const;\n}\n\nfunction rgbToHex(r: number, g: number, b: number) {\n  return (\n    \"#\" +\n    [r, g, b]\n      .map((v) =>\n        Math.max(0, Math.min(255, Math.round(v)))\n          .toString(16)\n          .padStart(2, \"0\"),\n      )\n      .join(\"\")\n  );\n}\n\nfunction darken(hex: string, amount: number) {\n  const [r, g, b] = hexToRgb(hex);\n  return rgbToHex(r * (1 - amount), g * (1 - amount), b * (1 - amount));\n}\n",
      "type": "registry:ui",
      "target": "components/ruixen/neon-circle-grid.tsx"
    }
  ]
}