{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flickering-grid",
  "type": "registry:component",
  "title": "Flickering grid",
  "description": "A canvas grid of small squares whose brightness flickers at random behind your content, like a board of quiet signals.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/flickering-grid.tsx",
      "type": "registry:ui",
      "target": "components/ui/flickering-grid.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { useReducedMotion } from \"motion/react\"\nimport { Activity } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\n/**\n * A canvas grid of small squares whose brightness flickers at random behind your content, like a board of quiet signals.\n * Colour comes from the accent token and an optional radial mask fades the\n * edges. The canvas is sharp on high density screens, resizes with its box,\n * sleeps off screen and draws one still frame for reduced motion.\n */\ntype FlickeringGridProps = {\n  /** Side of one square in pixels. */\n  squareSize?: number\n  /** Space between squares in pixels. */\n  gap?: number\n  /** Chance per second that any one square changes brightness, 0 to 1. */\n  flickerChance?: number\n  /** Brightest a square can get, 0 to 1. */\n  maxOpacity?: number\n  /** Any CSS colour, including var(--token). Defaults to the accent. */\n  color?: string\n  /** Fade the grid toward the edges. */\n  mask?: \"radial\" | \"none\"\n  className?: string\n  children?: React.ReactNode\n}\n\n/** Redraw cap. Flicker reads the same at 30fps and costs half. */\nconst FRAME = 1000 / 30\n\nfunction FlickeringGrid({\n  squareSize = 4,\n  gap = 6,\n  flickerChance = 0.4,\n  maxOpacity = 0.4,\n  color = \"var(--accent)\",\n  mask = \"radial\",\n  className,\n  children,\n}: FlickeringGridProps) {\n  const reduce = useReducedMotion()\n  const boxRef = React.useRef<HTMLDivElement>(null)\n  const canvasRef = React.useRef<HTMLCanvasElement>(null)\n\n  React.useEffect(() => {\n    const box = boxRef.current\n    const canvas = canvasRef.current\n    const ctx = canvas?.getContext(\"2d\")\n    if (!box || !canvas || !ctx) return\n\n    const sq = Math.max(1, squareSize)\n    const pitch = sq + Math.max(0, gap)\n    const top = Math.min(1, Math.max(0, maxOpacity))\n    let w = 0\n    let h = 0\n    let cols = 0\n    let rows = 0\n    let dpr = 1\n    let cells = new Float32Array(0)\n    let raf = 0\n    let last = 0\n    let drawn = 0\n    let visible = true\n    let fill = \"\"\n\n    const readColor = () => {\n      canvas.style.color = color\n      fill = getComputedStyle(canvas).color || \"currentColor\"\n    }\n\n    const resize = () => {\n      const r = box.getBoundingClientRect()\n      w = Math.max(1, Math.round(r.width))\n      h = Math.max(1, Math.round(r.height))\n      dpr = Math.min(window.devicePixelRatio || 1, 2)\n      canvas.width = Math.round(w * dpr)\n      canvas.height = Math.round(h * dpr)\n      const nc = Math.ceil(w / pitch)\n      const nr = Math.ceil(h / pitch)\n      if (nc === cols && nr === rows) return\n      // Keep the squares that still fit, seed the new ones.\n      const next = new Float32Array(nc * nr)\n      for (let y = 0; y < nr; y++)\n        for (let x = 0; x < nc; x++) next[y * nc + x] = x < cols && y < rows ? cells[y * cols + x] : Math.random() * top\n      cells = next\n      cols = nc\n      rows = nr\n    }\n\n    const draw = () => {\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n      ctx.clearRect(0, 0, w, h)\n      ctx.fillStyle = fill\n      // Centre the grid so the leftover space is shared by both edges.\n      const ox = Math.floor((w - (cols * pitch - (pitch - sq))) / 2)\n      const oy = Math.floor((h - (rows * pitch - (pitch - sq))) / 2)\n      for (let y = 0; y < rows; y++) {\n        for (let x = 0; x < cols; x++) {\n          const a = cells[y * cols + x]\n          if (a < 0.01) continue\n          ctx.globalAlpha = a\n          ctx.fillRect(ox + x * pitch, oy + y * pitch, sq, sq)\n        }\n      }\n      ctx.globalAlpha = 1\n    }\n\n    const step = (dt: number) => {\n      const p = Math.min(1, Math.max(0, flickerChance) * dt)\n      for (let i = 0; i < cells.length; i++) if (Math.random() < p) cells[i] = Math.random() * top\n    }\n\n    const loop = (t: number) => {\n      if (!last) last = drawn = t\n      if (t - drawn >= FRAME) {\n        step((t - last) / 1000)\n        last = t\n        drawn = t\n        draw()\n      }\n      raf = requestAnimationFrame(loop)\n    }\n    const stop = () => {\n      cancelAnimationFrame(raf)\n      raf = 0\n      last = 0\n    }\n    const sync = () => {\n      if (reduce) {\n        stop()\n        draw()\n        return\n      }\n      const run = visible && !document.hidden\n      if (run && !raf) raf = requestAnimationFrame(loop)\n      if (!run && raf) stop()\n    }\n\n    readColor()\n    resize()\n    const ro = new ResizeObserver(() => {\n      resize()\n      draw()\n    })\n    const io = new IntersectionObserver(([e]) => {\n      visible = e.isIntersecting\n      sync()\n    })\n    const mo = new MutationObserver(() => {\n      readColor()\n      draw()\n    })\n    ro.observe(box)\n    io.observe(box)\n    mo.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\", \"data-material\"] })\n    document.addEventListener(\"visibilitychange\", sync)\n    draw()\n    sync()\n\n    return () => {\n      stop()\n      ro.disconnect()\n      io.disconnect()\n      mo.disconnect()\n      document.removeEventListener(\"visibilitychange\", sync)\n    }\n  }, [squareSize, gap, flickerChance, maxOpacity, color, reduce])\n\n  const fade = mask === \"radial\" ? \"radial-gradient(ellipse 70% 70% at 50% 50%, black 25%, transparent 100%)\" : undefined\n\n  return (\n    <div\n      ref={boxRef}\n      data-slot=\"flickering-grid\"\n      className={cn(\"relative isolate h-[360px] w-full overflow-hidden rounded-2xl border border-border bg-bg\", className)}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden\n        data-slot=\"flickering-grid-canvas\"\n        className=\"pointer-events-none absolute inset-0 z-0 size-full\"\n        style={{ maskImage: fade, WebkitMaskImage: fade }}\n      />\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=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-surface px-2.5 py-0.5 text-xs font-medium text-fg-muted shadow-xs\">\n              <span className=\"size-1.5 rounded-full bg-success\" aria-hidden />\n              All Northwind systems normal\n            </span>\n            <p className=\"max-w-[16ch] text-3xl font-medium tracking-[-0.035em] text-balance text-fg tabular-nums\">\n              1,284 deploys shipped this week\n            </p>\n            <Button variant=\"outline\">\n              <Activity />\n              Open status page\n            </Button>\n          </div>\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport { FlickeringGrid }\nexport type { FlickeringGridProps }\n"
    }
  ]
}
