{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"grid-background","type":"registry:ui","title":"Grid Background","description":"A fine line grid that drifts slowly behind your content and fades out towards the edges","dependencies":[],"files":[{"path":"motion/grid-background/grid-background.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\n\n/*\n * Grid Background – a fine line grid behind your content, in a single self-contained file.\n * The grid drifts slowly by one cell per loop and fades out towards the edges through a radial mask.\n * On first view it can be uncovered by a circle that grows from the center.\n * Reduced motion: the grid stays still and appears without the reveal.\n */\n\nexport type GridDirection = \"up\" | \"down\" | \"left\" | \"right\" | \"diagonal\";\n\nexport type GridBackgroundProps = {\n  /** Content rendered above the grid */\n  children?: ReactNode;\n  /** Cell size in px */\n  size?: number;\n  /** Line color – defaults to a faint black, or a faint white in dark mode */\n  color?: string;\n  /** Seconds per cell; 0 keeps the grid still */\n  speed?: number;\n  /** Drift direction; \"diagonal\" runs from top right to bottom left */\n  direction?: GridDirection;\n  /** Uncover the grid with a growing circle when it first comes into view */\n  reveal?: boolean;\n  /** Radius of the edge fade, in % of the distance to the corners */\n  fade?: number;\n  className?: string;\n};\n\nconst offsets: Record<GridDirection, [number, number]> = {\n  up: [0, -1],\n  down: [0, 1],\n  left: [-1, 0],\n  right: [1, 0],\n  diagonal: [-1, 1],\n};\n\nexport function GridBackground({\n  children,\n  size = 48,\n  color,\n  speed = 3,\n  direction = \"diagonal\",\n  reveal = true,\n  fade = 70,\n  className,\n}: GridBackgroundProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const [shown, setShown] = useState(!reveal);\n\n  // Reveal once the grid scrolls into view (right away with reduced motion or without IntersectionObserver)\n  useEffect(() => {\n    if (!reveal) return setShown(true);\n    const el = ref.current;\n    if (!el || typeof IntersectionObserver === \"undefined\" || window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      setShown(true);\n      return;\n    }\n    const io = new IntersectionObserver(\n      (entries) => {\n        if (entries.some((e) => e.isIntersecting)) {\n          setShown(true);\n          io.disconnect();\n        }\n      },\n      { threshold: 0.2 },\n    );\n    io.observe(el);\n    return () => io.disconnect();\n  }, [reveal]);\n\n  const [dx, dy] = offsets[direction] ?? offsets.diagonal;\n  const style = {\n    \"--ui-gb-size\": `${size}px`,\n    \"--ui-gb-dx\": `${dx * size}px`,\n    \"--ui-gb-dy\": `${dy * size}px`,\n    \"--ui-gb-speed\": `${Math.max(speed, 0)}s`,\n    \"--ui-gb-fade\": `${fade}%`,\n    ...(color ? { \"--ui-gb-line\": color } : {}),\n  } as CSSProperties;\n\n  return (\n    <div\n      ref={ref}\n      className={[\"ui-gb\", className].filter(Boolean).join(\" \")}\n      style={style}\n      data-reveal={reveal ? \"\" : undefined}\n      data-shown={shown ? \"\" : undefined}\n      data-static={speed > 0 ? undefined : \"\"}\n    >\n      {/* React 19 hoists this into <head> once, no matter how many instances render */}\n      <style href=\"ui-grid-background\" precedence=\"default\">\n        {css}\n      </style>\n      <div className=\"ui-gb-grid\" aria-hidden=\"true\">\n        <div className=\"ui-gb-reveal\">\n          <div className=\"ui-gb-lines\" />\n        </div>\n      </div>\n      <div className=\"ui-gb-content\">{children}</div>\n    </div>\n  );\n}\n\nconst css = /* css */ `\n@property --ui-gb-r {\n  syntax: \"<percentage>\";\n  inherits: false;\n  initial-value: 0%;\n}\n\n.ui-gb {\n  --ui-gb-color: rgba(0, 0, 0, 0.07);\n  --ui-gb-ease: cubic-bezier(0.22, 1, 0.36, 1);\n  position: relative;\n  isolation: isolate;\n  overflow: hidden;\n}\n\n/* Edge fade: a still radial mask, so the drifting lines dissolve towards the sides */\n.ui-gb-grid {\n  position: absolute;\n  inset: 0;\n  z-index: 0;\n  pointer-events: none;\n  -webkit-mask-image: radial-gradient(ellipse at center, #000 20%, transparent var(--ui-gb-fade));\n  mask-image: radial-gradient(ellipse at center, #000 20%, transparent var(--ui-gb-fade));\n}\n\n/* Reveal: a circle from the center grows until it reaches the corners */\n.ui-gb-reveal {\n  position: absolute;\n  inset: 0;\n  overflow: hidden;\n}\n.ui-gb[data-reveal] .ui-gb-reveal {\n  --ui-gb-r: 0%;\n  -webkit-mask-image: radial-gradient(circle farthest-corner at center, #000 var(--ui-gb-r), transparent calc(var(--ui-gb-r) + 12%));\n  mask-image: radial-gradient(circle farthest-corner at center, #000 var(--ui-gb-r), transparent calc(var(--ui-gb-r) + 12%));\n}\n.ui-gb[data-reveal][data-shown] .ui-gb-reveal {\n  animation: ui-gb-reveal 1.6s var(--ui-gb-ease) forwards;\n}\n\n/* The lines: one cell larger on every side, so a shift by one cell loops seamlessly */\n.ui-gb-lines {\n  position: absolute;\n  inset: calc(var(--ui-gb-size) * -1);\n  background-image:\n    linear-gradient(to right, var(--ui-gb-line, var(--ui-gb-color)) 1px, transparent 1px),\n    linear-gradient(to bottom, var(--ui-gb-line, var(--ui-gb-color)) 1px, transparent 1px);\n  background-size: var(--ui-gb-size) var(--ui-gb-size);\n  animation: ui-gb-drift var(--ui-gb-speed) linear infinite;\n}\n.ui-gb[data-static] .ui-gb-lines {\n  animation: none;\n}\n\n.ui-gb-content {\n  position: relative;\n  z-index: 1;\n  height: 100%;\n}\n\n@keyframes ui-gb-drift {\n  from { transform: translate3d(0, 0, 0); }\n  to { transform: translate3d(var(--ui-gb-dx), var(--ui-gb-dy), 0); }\n}\n@keyframes ui-gb-reveal {\n  from { --ui-gb-r: 0%; }\n  to { --ui-gb-r: 100%; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-gb-lines { animation: none; }\n  .ui-gb[data-reveal] .ui-gb-reveal { -webkit-mask-image: none; mask-image: none; animation: none; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-gb {\n  --ui-gb-color: rgba(255, 255, 255, 0.07);\n}\n`;\n"}]}