{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "braille-loader",
  "title": "Braille Loader",
  "description": "An animated braille loading indicator with wave, typewriter, shimmer, and pulse variants.",
  "files": [
    {
      "path": "components/ui/braille-loader.tsx",
      "content": "import type { CSSProperties } from \"react\";\nimport { getBrailleCharacters } from \"@/lib/text-to-braille\";\nimport { cn } from \"@/lib/utils\";\n\ninterface BrailleLoaderProps {\n  text: string;\n  className?: string;\n  variant?: \"wave\" | \"typewriter\" | \"shimmer\" | \"pulse\";\n  speed?: number;\n}\n\nconst STEP_MS = 120;\nconst MIN_SPEED = 0.1;\n\nconst variantStyles: Record<\n  NonNullable<BrailleLoaderProps[\"variant\"]>,\n  string\n> = {\n  wave: `@keyframes braille-wave {\n    0%, 100% { opacity: 0.15; }\n    12%, 25% { opacity: 1; }\n    37% { opacity: 0.15; }\n  }`,\n  typewriter: `@keyframes braille-typewriter {\n    0%, 10% { opacity: 0; }\n    12% { opacity: 1; }\n    80% { opacity: 1; }\n    90%, 100% { opacity: 0; }\n  }`,\n  shimmer: `@keyframes braille-shimmer {\n    0%, 100% { opacity: 0.15; }\n    12%, 25% { opacity: 1; }\n    37% { opacity: 0.4; }\n    50% { opacity: 0.15; }\n  }`,\n  pulse: `@keyframes braille-pulse {\n    0%, 100% { opacity: 0.15; }\n    50% { opacity: 1; }\n  }`,\n};\n\nfunction getAnimation(\n  variant: NonNullable<BrailleLoaderProps[\"variant\"]>,\n  index: number,\n  total: number,\n  speed: number\n): CSSProperties {\n  const stepMs = STEP_MS / Math.max(speed, MIN_SPEED);\n  const totalDuration = (total + 2) * stepMs;\n  const delay = index * stepMs;\n\n  if (variant === \"pulse\") {\n    return {\n      animation: `braille-pulse ${total * stepMs * 1.5}ms ease-in-out ${delay}ms infinite`,\n    };\n  }\n\n  return {\n    animation: `braille-${variant} ${totalDuration}ms ease-in-out ${delay}ms infinite`,\n  };\n}\n\nexport function BrailleLoader({\n  text,\n  className,\n  variant = \"wave\",\n  speed = 1,\n}: BrailleLoaderProps) {\n  const chars = getBrailleCharacters(text);\n\n  return (\n    <span\n      aria-hidden=\"true\"\n      className={cn(\"inline-flex font-mono text-muted-foreground\", className)}\n    >\n      <style>{variantStyles[variant]}</style>\n      {chars.map(({ char, key }, i) => (\n        <span key={key} style={getAnimation(variant, i, chars.length, speed)}>\n          {char === \" \" ? \"\\u00A0\" : char}\n        </span>\n      ))}\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/braille-loader.tsx"
    },
    {
      "path": "lib/text-to-braille.ts",
      "content": "const braille: Record<string, string> = {\n  a: \"⠁\",\n  b: \"⠃\",\n  c: \"⠉\",\n  d: \"⠙\",\n  e: \"⠑\",\n  f: \"⠋\",\n  g: \"⠛\",\n  h: \"⠓\",\n  i: \"⠊\",\n  j: \"⠚\",\n  k: \"⠅\",\n  l: \"⠇\",\n  m: \"⠍\",\n  n: \"⠝\",\n  o: \"⠕\",\n  p: \"⠏\",\n  q: \"⠟\",\n  r: \"⠗\",\n  s: \"⠎\",\n  t: \"⠞\",\n  u: \"⠥\",\n  v: \"⠧\",\n  w: \"⠺\",\n  x: \"⠭\",\n  y: \"⠽\",\n  z: \"⠵\",\n  \" \": \" \",\n  \".\": \"⠲\",\n  \",\": \"⠂\",\n  \"?\": \"⠦\",\n  \"!\": \"⠖\",\n};\n\nexport function textToBraille(text: string): string {\n  return [...text]\n    .map((char) => {\n      const lower = char.toLowerCase();\n      const value = braille[lower] ?? char;\n\n      return char !== lower && braille[lower] ? `⠠${value}` : value;\n    })\n    .join(\"\");\n}\n\nexport function getBrailleCharacters(text: string) {\n  const seen = new Map<string, number>();\n\n  return [...textToBraille(text)].map((char) => {\n    const occurrence = seen.get(char) ?? 0;\n    seen.set(char, occurrence + 1);\n\n    return {\n      char,\n      key: `${char}-${occurrence}`,\n    };\n  });\n}\n",
      "type": "registry:lib",
      "target": "@lib/text-to-braille.ts"
    }
  ],
  "type": "registry:ui"
}
