{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-pattern",
  "type": "registry:ui",
  "title": "Dot Pattern",
  "description": "A resolution-independent dotted-grid background that fills its positioned container. Two render modes share one tiled SVG `<pattern>`:",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "decorative",
    "pattern"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json",
    "https://ds.interlace.tools/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/patterns/dot-pattern.tsx",
      "target": "components/ui/patterns/dot-pattern.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport {\n  type ComponentPropsWithoutRef,\n  forwardRef,\n  useEffect,\n  useId,\n  useImperativeHandle,\n  useRef,\n  useState,\n} from \"react\";\n\n// @interlace/dot-pattern v1.1.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/dot-pattern\n// What changed since: https://ds.interlace.tools/c/dot-pattern#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — DotPattern (decorative background pattern)\n *\n * A resolution-independent dotted-grid background that fills its positioned\n * container. Two render modes share one tiled SVG `<pattern>`:\n *\n *   - **static** (default): a single tiled `<rect>` paints the whole grid as\n *     one GPU-cheap fill — O(1) DOM nodes regardless of container size.\n *   - **glow**: a radial-gradient dot is animated per-cell. We still bound the\n *     work — glow renders individual `<circle>`s only for the measured tile\n *     count, gated entirely behind `prefers-reduced-motion`.\n *\n * | Rule | Concept                          | Where in this file                                                       |\n * | ---- | -------------------------------- | ------------------------------------------------------------------------ |\n * | R2   | Travels (high)                   | Pure decoration, zero product nouns — any site can drop it behind a hero |\n * | R4   | Extends native el + JSDoc        | `ComponentPropsWithoutRef<\"svg\">`; `@default` on every public prop       |\n * | R5   | `data-testid` typed, no default  | `\"data-testid\"?: string` — consumer supplies                            |\n * | R6   | `data-slot` on the root          | `data-slot=\"dot-pattern\"`                                                |\n * | R7   | className merged + ...rest + ref | `cn(...)` + `{...props}` + forwarded `ref`                               |\n * | R8   | Booleans default-false, no `is`  | `glow = false`                                                          |\n * | R18  | Tailwind only                    | No inline static styles; SVG geometry uses numeric attrs                |\n * | R19  | Tokens only                      | `currentColor` + the `color` prop; no raw hex / rgb / oklch             |\n * | R23  | CLS=0                            | `absolute inset-0` decorative chrome; no async height                   |\n * | R25  | `\"use client\"` only as needed    | Required — `useId` + motion + reduced-motion hook                       |\n * | R26  | a11y                             | `aria-hidden` + `pointer-events-none`; respects reduced motion          |\n *\n * ## API parity\n *\n * No MUI / shadcn analogue exists for a decorative dot grid; the closest\n * ecosystem reference is MagicUI's `DotPattern`, which inspired the visual\n * intent. We diverge deliberately: (1) the static mode tiles one SVG\n * `<pattern>` instead of emitting one `<motion.circle>` per cell, so a\n * full-bleed background is O(1) nodes rather than thousands; (2) dot color is\n * a first-class `color` prop defaulting to `currentColor` (the source\n * hard-coded a `text-neutral-400/80` class); (3) glow honors\n * `prefers-reduced-motion` and falls back to the static tile.\n */\n\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\ninterface DotPatternProps extends ComponentPropsWithoutRef<\"svg\"> {\n  /**\n   * Horizontal spacing between dot centers, in pixels.\n   * @default 16\n   */\n  spacingX?: number;\n  /**\n   * Vertical spacing between dot centers, in pixels.\n   * @default 16\n   */\n  spacingY?: number;\n  /**\n   * Horizontal offset of the whole tile, in pixels — shifts the grid origin.\n   * @default 0\n   */\n  offsetX?: number;\n  /**\n   * Vertical offset of the whole tile, in pixels — shifts the grid origin.\n   * @default 0\n   */\n  offsetY?: number;\n  /**\n   * Position of each dot within its own cell on the x axis, in pixels.\n   * @default 1\n   */\n  dotX?: number;\n  /**\n   * Position of each dot within its own cell on the y axis, in pixels.\n   * @default 1\n   */\n  dotY?: number;\n  /**\n   * Radius of each dot, in pixels.\n   * @default 1\n   */\n  radius?: number;\n  /**\n   * Dot color. Accepts any CSS color string, but prefer a design token or\n   * `currentColor` so the pattern inherits the surrounding text color — keep\n   * raw literals out of consuming source per the token contract.\n   * @default \"currentColor\"\n   */\n  color?: string;\n  /**\n   * Animate each dot with a soft radial pulse. Automatically disabled when the\n   * user requests reduced motion, falling back to the static tile.\n   * @default false\n   */\n  glow?: boolean;\n  /**\n   * Stable selector for E2E tests; consumer supplies — no runtime default.\n   */\n  \"data-testid\"?: string;\n}\n\ninterface GlowDot {\n  cx: number;\n  cy: number;\n  delay: number;\n  duration: number;\n}\n\nconst GLOW_DELAY_MAX_S = 5;\nconst GLOW_DURATION_MIN_S = 2;\nconst GLOW_DURATION_SPREAD_S = 3;\n\n/**\n * DotPattern — a tiled dotted-grid background for any positioned container.\n *\n * Render it as the first child of a `relative` parent; it fills the parent via\n * `absolute inset-0`, is `aria-hidden`, and never captures pointer events.\n *\n * @example\n * ```tsx\n * <div className=\"relative\">\n *   <DotPattern className=\"text-muted-foreground/40\" data-testid=\"hero-dots\" />\n *   <Content />\n * </div>\n * ```\n *\n * @example\n * ```tsx\n * // Glowing, wider grid (auto-static under prefers-reduced-motion)\n * <DotPattern glow spacingX={24} spacingY={24} data-testid=\"cta-dots\" />\n * ```\n */\nconst DotPattern = forwardRef<SVGSVGElement, DotPatternProps>(\n  function DotPattern(\n    {\n      spacingX = 16,\n      spacingY = 16,\n      offsetX = 0,\n      offsetY = 0,\n      dotX = 1,\n      dotY = 1,\n      radius = 1,\n      color = \"currentColor\",\n      glow = false,\n      className,\n      \"data-testid\": testId,\n      ...props\n    },\n    forwardedRef,\n  ) {\n    const id = useId();\n    const gradientId = `${id}-glow`;\n    const patternId = `${id}-tile`;\n\n    const reducedMotion = useReducedMotion();\n    const isGlowing = glow && !reducedMotion;\n\n    // Glow mode needs measured dimensions to lay out one animated circle per\n    // cell. Static mode never measures — the tiled <pattern> fills any size.\n    const svgRef = useRef<SVGSVGElement>(null);\n    useImperativeHandle(forwardedRef, () => svgRef.current as SVGSVGElement);\n\n    const [size, setSize] = useState({ width: 0, height: 0 });\n    useEffect(() => {\n      if (!isGlowing) return;\n      const node = svgRef.current;\n      if (!node) return;\n\n      const measure = () => {\n        const rect = node.getBoundingClientRect();\n        setSize({ width: rect.width, height: rect.height });\n      };\n      measure();\n\n      // ResizeObserver tracks container reflow without a global resize\n      // listener — fewer cross-component listeners, scoped to this node.\n      const observer = new ResizeObserver(measure);\n      observer.observe(node);\n      return () => observer.disconnect();\n    }, [isGlowing]);\n\n    const cols = spacingX > 0 ? Math.ceil(size.width / spacingX) : 0;\n    const rows = spacingY > 0 ? Math.ceil(size.height / spacingY) : 0;\n\n    const glowDots: GlowDot[] = isGlowing\n      ? Array.from({ length: cols * rows }, (_, i) => {\n          const col = i % cols;\n          const row = Math.floor(i / cols);\n          return {\n            cx: col * spacingX + offsetX + dotX,\n            cy: row * spacingY + offsetY + dotY,\n            delay: Math.random() * GLOW_DELAY_MAX_S,\n            duration:\n              Math.random() * GLOW_DURATION_SPREAD_S + GLOW_DURATION_MIN_S,\n          };\n        })\n      : [];\n\n    return (\n      <svg\n        ref={svgRef}\n        data-slot=\"dot-pattern\"\n        data-testid={testId}\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute inset-0 h-full w-full\",\n          className,\n        )}\n        {...props}\n      >\n        <defs>\n          <radialGradient id={gradientId}>\n            <stop offset=\"0%\" stopColor={color} stopOpacity=\"1\" />\n            <stop offset=\"100%\" stopColor={color} stopOpacity=\"0\" />\n          </radialGradient>\n          {!isGlowing && (\n            <pattern\n              id={patternId}\n              x={offsetX}\n              y={offsetY}\n              width={spacingX}\n              height={spacingY}\n              patternUnits=\"userSpaceOnUse\"\n              patternContentUnits=\"userSpaceOnUse\"\n            >\n              <circle cx={dotX} cy={dotY} r={radius} fill={color} />\n            </pattern>\n          )}\n        </defs>\n\n        {isGlowing ? (\n          glowDots.map((dot) => (\n            <motion.circle\n              key={`${dot.cx}-${dot.cy}`}\n              cx={dot.cx}\n              cy={dot.cy}\n              r={radius}\n              fill={`url(#${gradientId})`}\n              initial={{ opacity: 0.4, scale: 1 }}\n              animate={{ opacity: [0.4, 1, 0.4], scale: [1, 1.5, 1] }}\n              transition={{\n                duration: dot.duration,\n                repeat: Infinity,\n                repeatType: \"reverse\",\n                delay: dot.delay,\n                ease: \"easeInOut\",\n              }}\n            />\n          ))\n        ) : (\n          <rect width=\"100%\" height=\"100%\" fill={`url(#${patternId})`} />\n        )}\n      </svg>\n    );\n  },\n);\n\nexport { DotPattern };\nexport type { DotPatternProps };\n"
    }
  ],
  "meta": {
    "tier": "pattern",
    "client": true,
    "minViewport": null,
    "loading": false,
    "version": "1.1.0",
    "since": "1.0.0"
  },
  "docs": "## @interlace/dot-pattern\n\nInstalled to `components/ui/patterns/dot-pattern.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/patterns/dot-pattern';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/dot-pattern\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
