{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "skeleton",
  "title": "Skeleton",
  "description": "Placeholder block filled with a flowing dithered noise field that steps at 10 fps, shares one timer across every skeleton and holds still under reduced motion.",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [
    "@ui95/theme",
    "@ui95/dither-inks"
  ],
  "files": [
    {
      "path": "components/ui/skeleton.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"cn\"\nimport * as React from \"react\"\n\nimport { observeInkChange, parseCssColor } from \"@/lib/dither/inks\"\n\nconst FRAME_MS = 100\nconst DENSITY_LOW = 0.08\nconst DENSITY_HIGH = 0.62\n\ntype Surface = {\n  element: HTMLElement\n  canvas: HTMLCanvasElement\n  context: CanvasRenderingContext2D\n  pixel: number\n  back: number\n  front: number\n}\n\nconst surfaces = new Set<Surface>()\nlet timerId: number | null = null\nlet stopObservingInks: (() => void) | null = null\nlet startTime = 0\n\nfunction hash(x: number, y: number): number {\n  const value = Math.sin(x * 127.1 + y * 311.7) * 43758.5453\n  return value - Math.floor(value)\n}\n\nfunction readValueNoise(x: number, y: number): number {\n  const cellX = Math.floor(x)\n  const cellY = Math.floor(y)\n  const fractionX = x - cellX\n  const fractionY = y - cellY\n  const easeX = fractionX * fractionX * (3 - 2 * fractionX)\n  const easeY = fractionY * fractionY * (3 - 2 * fractionY)\n  const top = hash(cellX, cellY) + (hash(cellX + 1, cellY) - hash(cellX, cellY)) * easeX\n  const bottom = hash(cellX, cellY + 1) + (hash(cellX + 1, cellY + 1) - hash(cellX, cellY + 1)) * easeX\n  return top + (bottom - top) * easeY\n}\n\nfunction readDensity(x: number, y: number, time: number): number {\n  const u = x / 22\n  const v = y / 14\n  const warpX = readValueNoise(u * 0.7 + time * 0.18, v * 0.7 - time * 0.07)\n  const warpY = readValueNoise(u * 0.7 - time * 0.11 + 5.2, v * 0.7 + time * 0.13 + 1.3)\n  const flow = readValueNoise(u + warpX * 2.4 - time * 0.3, v + warpY * 2.4)\n  const fine = readValueNoise(u * 2.3 + time * 0.45, v * 2.3)\n  const level = Math.min(Math.max((flow * 0.75 + fine * 0.25 - 0.22) * 1.9, 0), 1)\n  return DENSITY_LOW + level * (DENSITY_HIGH - DENSITY_LOW)\n}\n\nfunction readBayer4(x: number, y: number): number {\n  const fine = ((x & 1) * 2 + (y & 1) * 3) & 3\n  const coarse = (((x >> 1) & 1) * 2 + ((y >> 1) & 1) * 3) & 3\n  return (fine * 4 + coarse + 0.5) / 16\n}\n\nfunction packColor(value: string): number {\n  const [red, green, blue, alpha] = parseCssColor(value)\n  return ((alpha << 24) | (blue << 16) | (green << 8) | red) >>> 0\n}\n\nfunction readTheme(surface: Surface) {\n  const style = getComputedStyle(surface.element)\n  const token = parseFloat(style.getPropertyValue(\"--pixel\"))\n  surface.pixel = Number.isFinite(token) && token > 0 ? token : 2\n  surface.back = packColor(style.getPropertyValue(\"--muted\").trim())\n  surface.front = packColor(style.getPropertyValue(\"--muted-foreground\").trim())\n}\n\nfunction paint(surface: Surface, time: number) {\n  const rect = surface.element.getBoundingClientRect()\n  if (rect.width === 0 || rect.height === 0) {\n    return\n  }\n  if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) {\n    return\n  }\n  const { canvas, context, pixel } = surface\n  const columns = Math.ceil(rect.width / pixel)\n  const rows = Math.ceil(rect.height / pixel)\n  if (canvas.width !== columns || canvas.height !== rows) {\n    canvas.width = columns\n    canvas.height = rows\n    canvas.style.width = `${columns * pixel}px`\n    canvas.style.height = `${rows * pixel}px`\n  }\n  const originX = Math.round((rect.left + window.scrollX) / pixel)\n  const originY = Math.round((rect.top + window.scrollY) / pixel)\n  const image = context.createImageData(columns, rows)\n  const out = new Uint32Array(image.data.buffer)\n  for (let row = 0; row < rows; row += 1) {\n    const worldY = originY + row\n    for (let column = 0; column < columns; column += 1) {\n      const worldX = originX + column\n      const isFront = readDensity(worldX, worldY, time) > readBayer4(worldX, worldY)\n      out[row * columns + column] = isFront ? surface.front : surface.back\n    }\n  }\n  context.putImageData(image, 0, 0)\n}\n\nfunction isReducedMotion(): boolean {\n  return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n}\n\nfunction readTime(): number {\n  return isReducedMotion() ? 0 : (performance.now() - startTime) / 1000\n}\n\nfunction paintAll() {\n  if (document.hidden) {\n    return\n  }\n  const time = Math.floor(readTime() * (1000 / FRAME_MS)) * (FRAME_MS / 1000)\n  for (const surface of surfaces) {\n    paint(surface, time)\n  }\n}\n\nfunction refreshTheme() {\n  for (const surface of surfaces) {\n    readTheme(surface)\n  }\n  paintAll()\n}\n\nfunction attachSurface(element: HTMLElement, canvas: HTMLCanvasElement): () => void {\n  const context = canvas.getContext(\"2d\")\n  if (!context) {\n    return () => {}\n  }\n  const surface: Surface = { element, canvas, context, pixel: 2, back: 0, front: 0 }\n  readTheme(surface)\n  if (surfaces.size === 0) {\n    startTime = performance.now()\n    stopObservingInks = observeInkChange(refreshTheme)\n    if (!isReducedMotion()) {\n      timerId = window.setInterval(paintAll, FRAME_MS)\n    }\n  }\n  surfaces.add(surface)\n  paint(surface, readTime())\n  const resizeObserver = new ResizeObserver(() => paint(surface, readTime()))\n  resizeObserver.observe(element)\n  return () => {\n    resizeObserver.disconnect()\n    surfaces.delete(surface)\n    if (surfaces.size === 0) {\n      if (timerId !== null) {\n        window.clearInterval(timerId)\n        timerId = null\n      }\n      stopObservingInks?.()\n      stopObservingInks = null\n    }\n  }\n}\n\nfunction Skeleton({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const elementRef = React.useRef<HTMLDivElement>(null)\n  const canvasRef = React.useRef<HTMLCanvasElement>(null)\n\n  React.useEffect(() => {\n    const element = elementRef.current\n    const canvas = canvasRef.current\n    if (!element || !canvas) {\n      return\n    }\n    return attachSurface(element, canvas)\n  }, [])\n\n  return (\n    <div\n      ref={elementRef}\n      data-slot=\"skeleton\"\n      className={cn(\"relative overflow-hidden bg-muted\", className)}\n      {...props}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute top-0 left-0 pixelated\"\n      />\n      {children}\n    </div>\n  )\n}\n\nexport { Skeleton }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "ui"
  ],
  "type": "registry:ui"
}