{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "background-boxes",
  "type": "registry:component",
  "title": "Background boxes",
  "description": "A skewed grid of hairline boxes behind your content where the box under the pointer lights up in colour and fades out behind it.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/background-boxes.tsx",
      "type": "registry:ui",
      "target": "components/ui/background-boxes.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\nimport { ArrowRight } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\n/**\n * A skewed grid of hairline boxes behind your content where the box under the pointer lights up in colour and fades out behind it.\n * One pointer handler maps the cursor into the skewed grid with a little\n * matrix maths and writes the colour straight to the box, so React never\n * re-renders a box. Neighbours catch a softer glow. It works through the\n * content on top and with touch. Left alone, a box lights now and then so\n * the panel looks alive; that idle flicker pauses off screen and stops for\n * reduced motion, and the fade is instant for reduced motion too.\n */\ntype BackgroundBoxesProps = {\n  /** Box density. 1 draws 64px boxes, 2 draws them half the size. */\n  density?: number\n  /** Colours the boxes light up in. Any CSS colour, including var(--token). */\n  colors?: string[]\n  /** Seconds a lit box takes to fade back. */\n  fade?: number\n  /** Boxes that light on their own each second while nobody hovers. 0 turns the idle flicker off. */\n  idle?: number\n  /** Tilt of the grid in degrees: skew along x, then along y. */\n  skew?: [number, number]\n  className?: string\n  children?: React.ReactNode\n}\n\ntype Geometry = { cols: number; rows: number; cell: number; gw: number; gh: number }\n\nfunction BackgroundBoxes({\n  density = 1,\n  colors = [\"var(--accent)\", \"var(--accent-2)\", \"var(--info)\", \"var(--success)\", \"var(--warning)\"],\n  fade = 1.6,\n  idle = 2,\n  skew = [-24, 12],\n  className,\n  children,\n}: BackgroundBoxesProps) {\n  const reduce = useReducedMotion()\n  const ref = React.useRef<HTMLDivElement>(null)\n  const inView = useInView(ref, { margin: \"80px\" })\n  const gridRef = React.useRef<HTMLDivElement>(null)\n  const current = React.useRef(-1)\n  const [geo, setGeo] = React.useState<Geometry | null>(null)\n  const palette = colors.length ? colors : [\"var(--accent)\"]\n  const cell = Math.max(12, Math.round(64 / Math.max(0.25, density)))\n  const [sx, sy] = skew\n  const ta = Math.tan((sx * Math.PI) / 180)\n  const tb = Math.tan((sy * Math.PI) / 180)\n\n  // Measure on the client only, so the server renders an empty grid and hydration matches.\n  React.useEffect(() => {\n    const el = ref.current\n    if (!el) return\n    const ro = new ResizeObserver(([e]) => {\n      const w = e.contentRect.width\n      const h = e.contentRect.height\n      const cols = Math.ceil((w * 1.6) / cell) + 1\n      const rows = Math.ceil((h * 2.1) / cell) + 1\n      setGeo((g) => (g && g.cols === cols && g.rows === rows && g.cell === cell ? g : { cols, rows, cell, gw: cols * cell, gh: rows * cell }))\n    })\n    ro.observe(el)\n    return () => ro.disconnect()\n  }, [cell])\n\n  const boxAt = (i: number) => gridRef.current?.children[i] as HTMLElement | undefined\n  const light = (i: number, pct: number) => {\n    const el = boxAt(i)\n    if (!el) return\n    el.style.transitionDuration = \"0s\"\n    el.style.backgroundColor = `color-mix(in oklch, var(--c) ${pct}%, transparent)`\n  }\n  const release = (i: number) => {\n    const el = boxAt(i)\n    if (!el) return\n    // Flush the lit state first, so the browser has something to transition from.\n    void getComputedStyle(el).backgroundColor\n    el.style.transitionDuration = \"\"\n    el.style.backgroundColor = \"\"\n  }\n\n  // Idle flicker: a box near the middle lights and lets go, on a fixed pseudo-random walk.\n  React.useEffect(() => {\n    if (!geo || reduce || !inView || idle <= 0) return\n    let seed = 3\n    const rand = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648\n    const tick = () => {\n      if (document.hidden) return\n      const col = Math.floor(geo.cols * (0.22 + 0.56 * rand()))\n      const row = Math.floor(geo.rows * (0.28 + 0.44 * rand()))\n      const i = row * geo.cols + col\n      if (i === current.current) return\n      light(i, 54)\n      requestAnimationFrame(() => {\n        if (i !== current.current) release(i)\n      })\n    }\n    const timer = window.setInterval(tick, Math.max(120, 1000 / idle))\n    return () => window.clearInterval(timer)\n    // light and release only touch refs.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [geo, reduce, inView, idle])\n\n  const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n    const el = ref.current\n    if (!el || !geo) return\n    const r = el.getBoundingClientRect()\n    // Pointer relative to the panel centre, then through the inverse skew into grid space.\n    const qx = e.clientX - r.left - r.width / 2\n    const qy = e.clientY - r.top - r.height / 2\n    const px = qx - ta * qy + geo.gw / 2\n    const py = -tb * qx + (1 + ta * tb) * qy + geo.gh / 2\n    const col = Math.floor(px / geo.cell)\n    const row = Math.floor(py / geo.cell)\n    const idx = col < 0 || row < 0 || col >= geo.cols || row >= geo.rows ? -1 : row * geo.cols + col\n    if (idx === current.current) return\n    if (current.current >= 0) release(current.current)\n    current.current = idx\n    if (idx < 0) return\n    light(idx, 64)\n    // Neighbours catch a softer glow and let go at once, so a trail forms behind the pointer.\n    const around = [col > 0 ? idx - 1 : -1, col < geo.cols - 1 ? idx + 1 : -1, row > 0 ? idx - geo.cols : -1, row < geo.rows - 1 ? idx + geo.cols : -1]\n    for (const n of around) if (n >= 0) light(n, 26)\n    requestAnimationFrame(() => {\n      for (const n of around) if (n >= 0 && n !== current.current) release(n)\n    })\n  }\n  const onPointerLeave = () => {\n    if (current.current >= 0) release(current.current)\n    current.current = -1\n  }\n\n  return (\n    <div\n      ref={ref}\n      data-slot=\"background-boxes\"\n      onPointerMove={onPointerMove}\n      onPointerLeave={onPointerLeave}\n      onPointerCancel={onPointerLeave}\n      className={cn(\"relative isolate h-[360px] w-full overflow-hidden rounded-2xl border border-border bg-bg\", className)}\n      style={{ \"--bb-fade\": reduce ? \"0s\" : `${Math.max(0, fade)}s` } as React.CSSProperties}\n    >\n      <div\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 z-0\"\n        style={{\n          maskImage: \"radial-gradient(120% 130% at 50% 50%, black 35%, transparent 100%)\",\n          WebkitMaskImage: \"radial-gradient(120% 130% at 50% 50%, black 35%, transparent 100%)\",\n        }}\n      >\n        {geo && (\n          <div\n            ref={gridRef}\n            data-slot=\"background-boxes-grid\"\n            className=\"absolute top-1/2 left-1/2 grid border-t border-l border-border will-change-transform\"\n            style={{\n              width: geo.gw,\n              height: geo.gh,\n              gridTemplateColumns: `repeat(${geo.cols}, ${geo.cell}px)`,\n              gridAutoRows: `${geo.cell}px`,\n              transform: `translate(-50%, -50%) skewX(${sx}deg) skewY(${sy}deg)`,\n            }}\n          >\n            {Array.from({ length: geo.cols * geo.rows }, (_, i) => {\n              const col = i % geo.cols\n              const row = (i - col) / geo.cols\n              const c = palette[(col * 7 + row * 11 + ((col * row) % 3)) % palette.length]\n              return (\n                <div\n                  key={i}\n                  data-slot=\"background-boxes-box\"\n                  className=\"border-r border-b border-border\"\n                  style={{ \"--c\": c } as React.CSSProperties}\n                />\n              )\n            })}\n          </div>\n        )}\n      </div>\n      <div className=\"relative z-10 h-full\">\n        {children ?? (\n          <div className=\"flex h-full flex-col items-center justify-center gap-4 px-6 text-center\">\n            <span className=\"rounded-full border border-accent-line bg-accent-soft px-2.5 py-0.5 text-xs font-medium text-accent-fg\">\n              Northwind on Lumen\n            </span>\n            <p className=\"max-w-[16ch] text-3xl font-medium tracking-[-0.035em] text-balance text-fg\">\n              Built on a grid you can feel\n            </p>\n            <Button>\n              Explore the system\n              <ArrowRight />\n            </Button>\n          </div>\n        )}\n      </div>\n      <style>{`[data-slot=\"background-boxes-box\"]{transition:background-color var(--bb-fade,1.6s) cubic-bezier(0.3,0,0.8,0.15)}`}</style>\n    </div>\n  )\n}\n\nexport { BackgroundBoxes }\nexport type { BackgroundBoxesProps }\n"
    }
  ]
}
