{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-background",
  "title": "Dot Background",
  "description": "Small marks behind your content: an even dot field, a fading one, plus marks or a halftone growing toward one corner, with an optional colour around the pointer.",
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/grid-background.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/dot-background.tsx",
      "content": "import type * as React from \"react\"\nimport { useEffect, useMemo, useRef, useState } from \"react\"\nimport { BackgroundPointerLayer, useBackgroundPointer } from \"@/registry/miseui/ui/grid-background-pointer\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype DotBackgroundVariant = \"dots\" | \"fade\" | \"plus\" | \"halftone\"\ntype DotBackgroundEffect = \"pointer\"\n\ninterface HalftoneBucket {\n  radius: number\n  /** One path per dot size, zero-length segments with round caps: a few thousand dots stay one element each. */\n  path: string\n}\n\nconst CELL = \"var(--mise-dot-cell,20px)\"\nconst DOT_IMAGE = \"radial-gradient(circle,var(--mise-dot) 1px,transparent 1.5px)\"\nconst DOTS_STYLE: React.CSSProperties = { backgroundImage: DOT_IMAGE, backgroundSize: `${CELL} ${CELL}` }\nconst FADE_MASK = \"radial-gradient(ellipse closest-side at 50% 50%,#000 20%,transparent 100%)\"\nconst FADE_STYLE: React.CSSProperties = { ...DOTS_STYLE, maskImage: FADE_MASK, WebkitMaskImage: FADE_MASK }\n\n/** A mask, because a data URI cannot read a theme token. Whole-pixel arms stay crisp at 1x. */\nconst PLUS_TILE = `url(\"data:image/svg+xml,${encodeURIComponent(\"<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24'><path d='M11 8h1v3h3v1h-3v3h-1v-3H8v-1h3z'/></svg>\")}\")`\nconst PLUS_STYLE: React.CSSProperties = { opacity: 0.7, backgroundColor: \"var(--mise-dot)\", maskImage: PLUS_TILE, WebkitMaskImage: PLUS_TILE, maskSize: \"24px 24px\", WebkitMaskSize: \"24px 24px\" }\n\nconst HALFTONE_STYLE: React.CSSProperties = { color: \"var(--mise-dot)\" }\nconst HALFTONE_CELL = 12\nconst HALFTONE_MAX_RADIUS = 4\nconst HALFTONE_MIN_RADIUS = 0.4\nconst HALFTONE_STEP = 0.4\nconst HALFTONE_CEILING = 8000\n/** Dots stop before the middle of the box, where the text sits, so the pattern stays in its corner at any size. */\nconst HALFTONE_REACH = 0.45\n\n/** Dot size grows toward the top right corner on a staggered grid. The cell widens on a huge box, so the count has a ceiling. */\nfunction halftoneBuckets(width: number, height: number): readonly HalftoneBucket[] {\n  if (width <= 0 || height <= 0) return []\n  const cell = Math.max(HALFTONE_CELL, Math.sqrt((width * height) / HALFTONE_CEILING))\n  const pitch = cell * 0.866\n  const reach = Math.hypot(width, height) * HALFTONE_REACH\n  const rows = Math.ceil(height / pitch) + 1\n  const cols = Math.ceil(width / cell) + 1\n  const segments = Array.from({ length: rows }).flatMap((_, row) =>\n    Array.from({ length: cols }).flatMap((__, col) => {\n      const x = col * cell + (row % 2 ? cell / 2 : 0)\n      const y = row * pitch\n      const closeness = 1 - Math.hypot(width - x, y) / reach\n      const radius = closeness > 0 ? HALFTONE_MAX_RADIUS * closeness ** 1.6 : 0\n      const stepped = Math.round(radius / HALFTONE_STEP) * HALFTONE_STEP\n      return stepped < HALFTONE_MIN_RADIUS ? [] : [{ radius: Number(stepped.toFixed(1)), segment: `M${x.toFixed(1)} ${y.toFixed(1)}h0` }]\n    }),\n  )\n  const sizes = [...new Set(segments.map((dot) => dot.radius))]\n  return sizes.map((radius) => ({\n    radius,\n    path: segments\n      .filter((dot) => dot.radius === radius)\n      .map((dot) => dot.segment)\n      .join(\"\"),\n  }))\n}\n\n/** Measures the layer, not the root, so the owner keeps their own ref on the root. */\nfunction useLayerSize(enabled: boolean) {\n  const ref = useRef<HTMLDivElement>(null)\n  const [size, setSize] = useState({ width: 0, height: 0 })\n  useEffect(() => {\n    const layer = ref.current\n    if (!enabled || !layer) return\n    const observer = new ResizeObserver(([entry]) => {\n      const width = Math.round(entry.contentRect.width)\n      const height = Math.round(entry.contentRect.height)\n      setSize((current) => (current.width === width && current.height === height ? current : { width, height }))\n    })\n    observer.observe(layer)\n    return () => observer.disconnect()\n  }, [enabled])\n  return { ref, size }\n}\n\nfunction Halftone({ buckets }: { buckets: readonly HalftoneBucket[] }) {\n  return (\n    <svg data-slot=\"dot-background-halftone\" className=\"absolute inset-0 size-full\" style={HALFTONE_STYLE} fill=\"none\" stroke=\"currentColor\" strokeLinecap=\"round\">\n      {buckets.map((bucket) => (\n        <path key={bucket.radius} d={bucket.path} strokeWidth={bucket.radius * 2} />\n      ))}\n    </svg>\n  )\n}\n\nfunction Marks({ variant, buckets }: { variant: DotBackgroundVariant; buckets: readonly HalftoneBucket[] }) {\n  if (variant === \"halftone\") return <Halftone buckets={buckets} />\n  const style = variant === \"fade\" ? FADE_STYLE : variant === \"plus\" ? PLUS_STYLE : DOTS_STYLE\n  return <div data-slot=\"dot-background-marks\" className=\"absolute inset-0\" style={style} />\n}\n\ntype DotBackgroundProps = React.ComponentProps<\"div\"> & {\n  /** `dots` an even field, `fade` dots dissolve toward the edges, `plus` small plus marks, `halftone` dots grow toward the top right corner. */\n  variant?: DotBackgroundVariant\n  /** `pointer` colours the marks around the pointer. Off on touch. */\n  effect?: DotBackgroundEffect\n}\n\n/** Change the spacing of `dots` and `fade` from the outside: `className=\"[--mise-dot-cell:24px]\"`. */\nfunction DotBackground({ variant = \"dots\", effect, className, children, onPointerMove, onPointerLeave, ...props }: DotBackgroundProps) {\n  const { lit, handlers } = useBackgroundPointer(effect === \"pointer\", { onPointerMove, onPointerLeave })\n  const { ref, size } = useLayerSize(variant === \"halftone\")\n  const buckets = useMemo(() => (variant === \"halftone\" ? halftoneBuckets(size.width, size.height) : []), [variant, size.width, size.height])\n  return (\n    <div\n      {...props}\n      {...handlers}\n      data-slot=\"dot-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        ref={ref}\n        aria-hidden=\"true\"\n        data-slot=\"dot-background-layer\"\n        className=\"pointer-events-none absolute inset-0 rounded-[inherit] [--mise-dot:color-mix(in_oklab,var(--foreground)_22%,transparent)] contrast-more:hidden forced-colors:hidden\"\n      >\n        <Marks variant={variant} buckets={buckets} />\n        {effect === \"pointer\" ? (\n          <BackgroundPointerLayer lit={lit} className=\"[--mise-dot:var(--mise)]\">\n            <Marks variant={variant} buckets={buckets} />\n          </BackgroundPointerLayer>\n        ) : null}\n      </div>\n      <div data-slot=\"dot-background-content\" className=\"relative h-full min-h-[inherit]\">\n        {children}\n      </div>\n    </div>\n  )\n}\n\nexport { DotBackground, halftoneBuckets, type DotBackgroundProps, type DotBackgroundVariant, type DotBackgroundEffect, type HalftoneBucket }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "backgrounds"
  ],
  "type": "registry:ui"
}