{"$schema":"https://ui.shadcn.com/schema/registry.json","author":"Kaiyu Hsu <uicapsule@kyh.io>","categories":["effects","geometric"],"dependencies":[],"description":"Generative glyph grid background — two mirrored SVG panels of dots, crosses, stripes and squares, each cell swapping to a random shape on its own timer.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ReactElement, ReactNode } from \"react\";\n\ninterface CellProps {\n  colors: string[];\n  strokeWidth: number;\n}\n\ntype CellComponent = (props: CellProps) => ReactElement | null;\n\n// Cell glyph functions that return JSX instead of SVG strings\nconst Cell1 = ({ colors }: CellProps) => (\n  <circle cx=\"50\" cy=\"50\" r=\"9.44\" fill={colors[0]} fillRule=\"evenodd\" />\n);\n\nconst Cell2 = ({ colors, strokeWidth }: CellProps) => (\n  <>\n    <line x1=\"25\" x2=\"75\" y1=\"25\" y2=\"25\" stroke={colors[0]} strokeWidth={strokeWidth} />\n    <line x1=\"25\" x2=\"75\" y1=\"50\" y2=\"50\" stroke={colors[0]} strokeWidth={strokeWidth} />\n    <line x1=\"25\" x2=\"75\" y1=\"75\" y2=\"75\" stroke={colors[0]} strokeWidth={strokeWidth} />\n  </>\n);\n\nconst Cell3 = ({ colors, strokeWidth }: CellProps) => (\n  <>\n    <line x1=\"25\" x2=\"75\" y1=\"25\" y2=\"75\" stroke={colors[0]} strokeWidth={strokeWidth} />\n    <line x1=\"25\" x2=\"75\" y1=\"75\" y2=\"25\" stroke={colors[0]} strokeWidth={strokeWidth} />\n  </>\n);\n\nconst Cell4 = ({ colors, strokeWidth }: CellProps) => (\n  <rect\n    width=\"50\"\n    height=\"50\"\n    x=\"25\"\n    y=\"25\"\n    fill=\"none\"\n    stroke={colors[0]}\n    strokeWidth={strokeWidth}\n  />\n);\n\nconst Cell5 = ({ colors, strokeWidth }: CellProps) => (\n  <line x1=\"25\" x2=\"75\" y1=\"75\" y2=\"25\" fill=\"none\" stroke={colors[0]} strokeWidth={strokeWidth} />\n);\n\nconst Cell6 = () => null;\n\nconst Cell7 = () => <rect width=\"75\" height=\"75\" x=\"12.5\" y=\"12.5\" fill=\"rgba(255,255,255,0.1)\" />;\n\ninterface CellConfig {\n  glyph: CellComponent;\n  weight: number;\n}\n\nconst cellConfigs: CellConfig[] = [\n  { glyph: Cell1, weight: 1 },\n  { glyph: Cell2, weight: 1 },\n  { glyph: Cell3, weight: 1 },\n  { glyph: Cell4, weight: 1 },\n  { glyph: Cell5, weight: 1 },\n  { glyph: Cell6, weight: 5 },\n  { glyph: Cell7, weight: 3 },\n];\n\n// Each config repeated `weight` times, so a uniform draw honours the weights.\n// Built once at module scope: every cell re-rolls on its own timer, so rebuilding\n// this per draw would allocate constantly.\nconst weightedCells: CellConfig[] = cellConfigs.flatMap((config) =>\n  Array.from({ length: config.weight }, () => config),\n);\n\n// Unreachable fallback for `noUncheckedIndexedAccess`; the index below is always in range.\nconst fallbackCell: CellConfig = { glyph: Cell1, weight: 1 };\n\nconst pickCell = (): CellConfig =>\n  weightedCells[Math.floor(Math.random() * weightedCells.length)] ?? fallbackCell;\n\n// Cells are authored in a 100x100 viewBox; this maps one down to a `cellSize` grid slot.\nconst CELL_SCALE = 0.2;\n\n// Module scope so the default keeps a stable identity across renders.\nconst DEFAULT_COLORS = [\"white\"];\n\n// Individual cell component that manages its own interval\ninterface GridCellProps {\n  x: number;\n  y: number;\n  colors: string[];\n  strokeWidth: number;\n  minInterval: number;\n  maxInterval: number;\n}\n\nconst GridCell = ({ x, y, colors, strokeWidth, minInterval, maxInterval }: GridCellProps) => {\n  const [currentCell, setCurrentCell] = useState<CellConfig>(pickCell);\n\n  useEffect(() => {\n    const getRandomInterval = () => Math.random() * (maxInterval - minInterval) + minInterval;\n\n    let timeoutId: ReturnType<typeof setTimeout>;\n    const scheduleNext = () => {\n      timeoutId = setTimeout(() => {\n        setCurrentCell(pickCell());\n        scheduleNext();\n      }, getRandomInterval());\n    };\n    scheduleNext();\n\n    return () => clearTimeout(timeoutId);\n  }, [minInterval, maxInterval]);\n\n  const Glyph = currentCell.glyph;\n\n  return (\n    <g transform={`translate(${x} ${y})`}>\n      <g transform={`scale(${CELL_SCALE})`}>\n        <Glyph colors={colors} strokeWidth={strokeWidth} />\n      </g>\n    </g>\n  );\n};\n\ninterface BackgroundGlyphsProps {\n  width?: number;\n  height?: number;\n  cellSize?: number;\n  strokeWidth?: number;\n  colors?: string[];\n  className?: string;\n  minInterval?: number;\n  maxInterval?: number;\n}\n\nexport const BackgroundGlyphs = ({\n  width = 500,\n  height = 500,\n  cellSize = 20,\n  strokeWidth = 10,\n  colors = DEFAULT_COLORS,\n  className = \"\",\n  minInterval = 1000,\n  maxInterval = 5000,\n}: BackgroundGlyphsProps) => {\n  const borderSize = cellSize * 2;\n\n  const cells = useMemo<ReactNode[]>(() => {\n    const list: ReactNode[] = [];\n    for (let x = borderSize; x < width / 2; x += cellSize) {\n      for (let y = borderSize; y < height - borderSize; y += cellSize) {\n        list.push(\n          <GridCell\n            key={`left-${x}-${y}`}\n            x={x}\n            y={y}\n            colors={colors}\n            strokeWidth={strokeWidth}\n            minInterval={minInterval}\n            maxInterval={maxInterval}\n          />,\n          <GridCell\n            key={`right-${x}-${y}`}\n            x={width - cellSize - x}\n            y={y}\n            colors={colors}\n            strokeWidth={strokeWidth}\n            minInterval={minInterval}\n            maxInterval={maxInterval}\n          />,\n        );\n      }\n    }\n    return list;\n  }, [width, height, cellSize, strokeWidth, colors, borderSize, minInterval, maxInterval]);\n\n  return (\n    <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={className}>\n      {cells}\n    </svg>\n  );\n};\n","path":"/background-shapes.tsx","target":"uicapsule/background-shapes/background-shapes.tsx","type":"registry:file"},{"content":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nimport { BackgroundGlyphs } from \"./background-shapes\";\n\nconst Preview = () => {\n  const [size, setSize] = useState<{ width: number; height: number } | null>(null);\n\n  useEffect(() => {\n    const update = () => setSize({ height: window.innerHeight, width: window.innerWidth });\n    update();\n    window.addEventListener(\"resize\", update);\n    return () => window.removeEventListener(\"resize\", update);\n  }, []);\n\n  return (\n    <div className=\"h-full w-full bg-[#2164D6]\">\n      {size && <BackgroundGlyphs width={size.width} height={size.height} />}\n    </div>\n  );\n};\n\nexport default Preview;\n","path":"/preview.tsx","target":"uicapsule/background-shapes/preview.tsx","type":"registry:file"}],"homepage":"https://www.uicapsule.com/ui/background-shapes","name":"background-shapes","registryDependencies":[],"title":"Background Shapes","type":"registry:block"}