{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "grid-background",
  "title": "Grid Background",
  "description": "Hairlines behind your content: an even grid, a fading one, a floor receding to a horizon or a few filled cells, with an optional brightening around the pointer.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/grid-background.tsx",
      "content": "import type * as React from \"react\"\nimport { useMemo } from \"react\"\nimport { BackgroundPointerLayer, useBackgroundPointer } from \"@/registry/miseui/ui/grid-background-pointer\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype GridBackgroundVariant = \"lines\" | \"fade\" | \"perspective\" | \"blocks\"\ntype GridBackgroundEffect = \"pointer\"\n\ninterface GridBlock {\n  col: number\n  row: number\n  /** 0..1, weaker further from the corner the blocks gather in. */\n  strength: number\n}\n\nconst CELL = \"var(--mise-grid-cell,32px)\"\nconst LINE_IMAGE = \"linear-gradient(var(--mise-grid-line) 1px,transparent 1px),linear-gradient(90deg,var(--mise-grid-line) 1px,transparent 1px)\"\n/** The -1 px hides the first line, which would otherwise double the owner's border. */\nconst LINES_STYLE: React.CSSProperties = { backgroundImage: LINE_IMAGE, backgroundSize: `${CELL} ${CELL}`, backgroundPosition: \"-1px -1px\" }\n/** Anchored top right like the blocks, so every block sits exactly between two lines. */\nconst BLOCK_LINES_STYLE: React.CSSProperties = { ...LINES_STYLE, backgroundPosition: \"100% -1px\" }\n\nconst FADE_MASK = \"radial-gradient(ellipse closest-side at 50% 50%,#000 20%,transparent 100%)\"\nconst FADE_STYLE: React.CSSProperties = { ...LINES_STYLE, maskImage: FADE_MASK, WebkitMaskImage: FADE_MASK }\n\n/**\n * The floor is hinged at the bottom edge and tips almost flat, so its lines meet at a horizon near 40 percent whatever\n * the box height. Everything is in container units to keep that picture the same on a card and a hero. 88 degrees rather\n * than 90 keeps the matrix away from the edge-on case. The plane is far larger than the box because the far rows shrink\n * to a ninth; the mask ends before its far edge, so that edge never shows. Depth squeezes rows, hence 2 px rows and 1 px columns.\n */\nconst FLOOR_MASK = \"linear-gradient(to bottom,transparent 46%,#000 100%)\"\nconst FLOOR_CELL = `max(${CELL},14cqh)`\nconst STAGE_STYLE: React.CSSProperties = { perspective: \"100cqh\", perspectiveOrigin: \"50% 42%\", maskImage: FLOOR_MASK, WebkitMaskImage: FLOOR_MASK }\nconst PLANE_STYLE: React.CSSProperties = {\n  left: \"-400cqw\",\n  width: \"900cqw\",\n  bottom: 0,\n  height: \"800cqh\",\n  transformOrigin: \"50% 100%\",\n  transform: \"rotateX(88deg)\",\n  backgroundImage: \"linear-gradient(var(--mise-grid-line) 2px,transparent 2px),linear-gradient(90deg,var(--mise-grid-line) 1px,transparent 1px)\",\n  backgroundSize: `${FLOOR_CELL} ${FLOOR_CELL}`,\n  backgroundPosition: \"50% 100%\",\n}\n\nconst BLOCK_COUNT = 9\nconst BLOCK_COLS = 10\nconst BLOCK_ROWS = 7\nconst BLOCK_ATTEMPTS = 160\n\n/** Pure hash of a seed and a position in the sequence, so a layout needs no random state and hydrates exactly. */\nfunction seededUnit(seed: number, index: number) {\n  const start = (seed + Math.imul(index + 1, 0x6d2b79f5)) | 0\n  const mixed = Math.imul(start ^ (start >>> 15), 1 | start)\n  const scrambled = mixed ^ (mixed + Math.imul(mixed ^ (mixed >>> 7), 61 | mixed))\n  return ((scrambled ^ (scrambled >>> 14)) >>> 0) / 4294967296\n}\n\nconst touches = (a: GridBlock, b: Pick<GridBlock, \"col\" | \"row\">) => Math.abs(a.col - b.col) + Math.abs(a.row - b.row) <= 1\n\n/** Picks cells counted from the top right corner. Squaring pulls them toward it, and no two share a side: a row of filled cells reads as a chart. */\nfunction pickGridBlocks(seed: number, count = BLOCK_COUNT, cols = BLOCK_COLS, rows = BLOCK_ROWS): readonly GridBlock[] {\n  return Array.from({ length: BLOCK_ATTEMPTS }).reduce<readonly GridBlock[]>((picked, _, attempt) => {\n    if (picked.length >= count) return picked\n    const cell = {\n      col: Math.floor(seededUnit(seed, attempt * 2) ** 2 * cols),\n      row: Math.floor(seededUnit(seed, attempt * 2 + 1) ** 2 * rows),\n    }\n    if (picked.some((block) => touches(block, cell))) return picked\n    const reach = Math.hypot(cell.col / cols, cell.row / rows) / Math.SQRT2\n    return [...picked, { ...cell, strength: Number((1 - reach * 0.7).toFixed(2)) }]\n  }, [])\n}\n\nfunction Lines({ variant }: { variant: GridBackgroundVariant }) {\n  if (variant === \"perspective\") {\n    return (\n      <div className=\"absolute inset-0 [container-type:size]\">\n        <div className=\"absolute inset-0\" style={STAGE_STYLE}>\n          <div data-slot=\"grid-background-plane\" className=\"absolute\" style={PLANE_STYLE} />\n        </div>\n      </div>\n    )\n  }\n  const style = variant === \"fade\" ? FADE_STYLE : variant === \"blocks\" ? BLOCK_LINES_STYLE : LINES_STYLE\n  return <div data-slot=\"grid-background-lines\" className=\"absolute inset-0\" style={style} />\n}\n\nfunction Blocks({ seed }: { seed: number }) {\n  const blocks = useMemo(() => pickGridBlocks(seed), [seed])\n  return blocks.map((block) => (\n    <div\n      key={`${block.col}-${block.row}`}\n      data-slot=\"grid-background-block\"\n      className=\"absolute bg-mise-soft\"\n      style={{\n        top: `calc(${CELL} * ${block.row} - 1px)`,\n        right: `calc(${CELL} * ${block.col})`,\n        width: CELL,\n        height: CELL,\n        opacity: block.strength,\n      }}\n    />\n  ))\n}\n\ntype GridBackgroundProps = React.ComponentProps<\"div\"> & {\n  /** `lines` even hairlines, `fade` lines dissolve toward the edges, `perspective` a floor receding to a horizon, `blocks` a few filled cells in the top right. */\n  variant?: GridBackgroundVariant\n  /** `pointer` brightens the lines around the pointer. Off on touch. */\n  effect?: GridBackgroundEffect\n  /** Which cells `blocks` fills. The same seed always gives the same cells. */\n  seed?: number\n}\n\n/** Change the cell size from the outside: `className=\"[--mise-grid-cell:48px]\"`. */\nfunction GridBackground({ variant = \"lines\", effect, seed = 7, className, children, onPointerMove, onPointerLeave, ...props }: GridBackgroundProps) {\n  const { lit, handlers } = useBackgroundPointer(effect === \"pointer\", { onPointerMove, onPointerLeave })\n  return (\n    <div\n      {...props}\n      {...handlers}\n      data-slot=\"grid-background\"\n      data-variant={variant}\n      data-effect={effect}\n      data-lit={lit ? \"true\" : undefined}\n      className={cn(\"relative isolate overflow-clip\", className)}\n    >\n      <div\n        aria-hidden=\"true\"\n        data-slot=\"grid-background-layer\"\n        className=\"pointer-events-none absolute inset-0 rounded-[inherit] [--mise-grid-line:var(--border)] contrast-more:hidden forced-colors:hidden\"\n      >\n        {variant === \"blocks\" ? <Blocks seed={seed} /> : null}\n        <Lines variant={variant} />\n        {effect === \"pointer\" ? (\n          <BackgroundPointerLayer lit={lit} className=\"[--mise-grid-line:color-mix(in_oklab,var(--mise)_70%,transparent)]\">\n            <Lines variant={variant} />\n          </BackgroundPointerLayer>\n        ) : null}\n      </div>\n      <div data-slot=\"grid-background-content\" className=\"relative h-full min-h-[inherit]\">\n        {children}\n      </div>\n    </div>\n  )\n}\n\nexport { GridBackground, pickGridBlocks, seededUnit, type GridBackgroundProps, type GridBackgroundVariant, type GridBackgroundEffect, type GridBlock }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/grid-background-pointer.tsx",
      "content": "import type * as React from \"react\"\nimport { useState } from \"react\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype PointerHandlers = Pick<React.HTMLAttributes<HTMLDivElement>, \"onPointerMove\" | \"onPointerLeave\">\n\nconst POINTER_MASK = \"radial-gradient(180px circle at var(--mise-bg-x) var(--mise-bg-y), #000, transparent)\"\nconst POINTER_STYLE: React.CSSProperties = { maskImage: POINTER_MASK, WebkitMaskImage: POINTER_MASK }\n\nfunction pointerOffset(clientX: number, clientY: number, box: Pick<DOMRect, \"left\" | \"top\">) {\n  return { x: clientX - box.left, y: clientY - box.top }\n}\n\n/**\n * Listens on the box itself, never on window, so a background off screen costs nothing. Events from the content above\n * bubble up to it. The position goes straight into custom properties: nothing re-renders while the pointer moves.\n */\nfunction useBackgroundPointer(enabled: boolean, { onPointerMove, onPointerLeave }: PointerHandlers) {\n  const [lit, setLit] = useState(false)\n  const handlers: PointerHandlers = {\n    onPointerMove: (event) => {\n      onPointerMove?.(event)\n      // A finger has no hover to follow, and a light stuck where it last touched would look like a bug.\n      if (!enabled || event.pointerType === \"touch\") return\n      const box = event.currentTarget\n      const { x, y } = pointerOffset(event.clientX, event.clientY, box.getBoundingClientRect())\n      box.style.setProperty(\"--mise-bg-x\", `${x}px`)\n      box.style.setProperty(\"--mise-bg-y\", `${y}px`)\n      setLit(true)\n    },\n    onPointerLeave: (event) => {\n      onPointerLeave?.(event)\n      setLit(false)\n    },\n  }\n  return { lit, handlers }\n}\n\n/** A second copy of the drawing in a brighter colour, shown only inside a circle around the pointer. */\nfunction BackgroundPointerLayer({ lit, className, children }: { lit: boolean; className?: string; children: React.ReactNode }) {\n  return (\n    <div\n      data-slot=\"background-pointer-layer\"\n      className={cn(\"absolute inset-0 transition-opacity duration-(--duration-slow) ease-(--ease-mise) motion-reduce:transition-none\", lit ? \"opacity-100\" : \"opacity-0\", className)}\n      style={POINTER_STYLE}\n    >\n      {children}\n    </div>\n  )\n}\n\nexport { BackgroundPointerLayer, pointerOffset, useBackgroundPointer }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "backgrounds"
  ],
  "type": "registry:ui"
}