{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"pixel-loader","type":"registry:ui","title":"PixelLoader","description":"Grid of pixels pulsing in a diagonal wave as a pre-token loading indicator for AI responses","categories":["ai-chat"],"registryDependencies":["https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/pixel-loader/PixelLoader.tsx","content":"\"use client\";\n\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport { cn } from \"../../utils\";\nimport \"./pixel-loader.css\";\n\nexport interface PixelLoaderProps extends HTMLAttributes<HTMLDivElement> {\n  /** Number of pixel columns */\n  cols?: number;\n  /** Number of pixel rows */\n  rows?: number;\n  /** Size of a single pixel in px */\n  cellSize?: number;\n  /** Space between pixels in px */\n  gap?: number;\n  /** Pixel colour, any CSS colour value */\n  color?: string;\n  /** Duration of one full pulse cycle in seconds */\n  speed?: number;\n  /** Accessible label announced by assistive tech */\n  label?: string;\n}\n\nexport const PixelLoader = forwardRef<HTMLDivElement, PixelLoaderProps>(\n  (\n    {\n      cols = 5,\n      rows = 5,\n      cellSize = 6,\n      gap = 2,\n      color = \"var(--ft-pixel-color, currentColor)\",\n      speed = 1.6,\n      label = \"Loading\",\n      className,\n      style,\n      ...rest\n    },\n    ref\n  ) => {\n    // A zero or negative animation-duration invalidates the whole declaration.\n    const cycle = Math.max(0.01, speed);\n\n    // Delays are a pure function of (row, col) so the server and the client\n    // render byte-identical markup — no Math.random, no post-hydration shuffle.\n    // (row + col) walks the anti-diagonals, which reads as a wave crossing the\n    // grid from the top-left corner to the bottom-right one.\n    const span = rows + cols;\n    const step = cycle / span;\n    const delays: number[] = [];\n    for (let row = 0; row < rows; row++) {\n      for (let col = 0; col < cols; col++) {\n        delays.push(((row + col) % span) * step);\n      }\n    }\n\n    const rootStyle = {\n      \"--ft-pixel-cell-color\": color,\n      \"--ft-pixel-speed\": `${cycle}s`,\n      gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`,\n      gridAutoRows: `${cellSize}px`,\n      gap: `${gap}px`,\n      ...style,\n    } as React.CSSProperties;\n\n    return (\n      <div\n        {...rest}\n        ref={ref}\n        role=\"status\"\n        aria-live=\"polite\"\n        className={cn(\"inline-grid\", className)}\n        style={rootStyle}\n      >\n        <span className=\"sr-only\">{label}</span>\n        {delays.map((delay, i) => (\n          <span key={i} className=\"ft-pixel\" style={{ animationDelay: `${delay}s` }} />\n        ))}\n      </div>\n    );\n  }\n);\n\nPixelLoader.displayName = \"PixelLoader\";\n","type":"registry:ui","target":"@ui/fancy/components/pixel-loader/PixelLoader.tsx"},{"path":"react/src/components/pixel-loader/index.ts","content":"\"use client\";\n\nexport { PixelLoader } from \"./PixelLoader\";\nexport type { PixelLoaderProps } from \"./PixelLoader\";\n","type":"registry:ui","target":"@ui/fancy/components/pixel-loader/index.ts"},{"path":"react/src/components/pixel-loader/pixel-loader.css","content":".ft-pixel {\n  background-color: var(--ft-pixel-cell-color);\n  border-radius: 1px;\n  /*\n   * Reduced motion holds every pixel at the mid-pulse value: the grid still\n   * reads as a loader, it just stops moving. Putting the animation entirely\n   * behind the no-preference query means there is nothing to override.\n   */\n  opacity: 0.55;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n  .ft-pixel {\n    /* `backwards` keeps a delayed pixel dim instead of flashing at 0.55\n       until its turn comes around. */\n    animation: ft-pixel-pulse var(--ft-pixel-speed) ease-in-out infinite backwards;\n  }\n\n  @keyframes ft-pixel-pulse {\n    0%,\n    100% {\n      opacity: 0.15;\n    }\n    50% {\n      opacity: 1;\n    }\n  }\n}\n","type":"registry:ui","target":"@ui/fancy/components/pixel-loader/pixel-loader.css"}]}