{ "$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 {\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 \n {/* Main ring — thick stroke = perfectly centred donut */}\n \n {/* Outer rim — dark edge */}\n \n {/* Inner rim — dark edge around hole */}\n \n {/* Mid-band highlight for subtle depth */}\n \n \n );\n\n return (\n \n \n \n \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 \n \n \n \n ) : (\n \n \n \n \n )}\n \n \n \n\n {/* ── Mask B: bright-colour blend (kicks in later in the fade) ── */}\n {isCenter ? (\n \n \n \n \n ) : (\n \n \n \n \n )}\n \n \n \n\n {/* ── Soft glow filter ── */}\n \n \n \n \n \n\n {/* Layer 1 — green circles (glow) */}\n \n {/* Layer 2 — green circles (sharp) */}\n \n {/* Layer 3 — cream/bright circles on top (blends in on bright side) */}\n \n \n \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" } ] }