{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drip-loaders",
  "type": "registry:component",
  "title": "Drip Loaders",
  "description": "Three loaders made of liquid in whatever colour you like: a comet of drops that stretches and gathers, a bead that drips into a pool, and a dot that splits and flows back together.",
  "categories": [
    "motion",
    "Liquid"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/drip-loaders.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { motion, useInView, useReducedMotion } from \"motion/react\";\nimport { type ComponentProps, type ReactNode, useId, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type DripLoaderVariant = \"spin\" | \"drip\" | \"split\";\n\nexport interface DripLoaderProps extends Omit<ComponentProps<\"span\">, \"children\"> {\n  variant?: DripLoaderVariant;\n  size?: number;\n  speed?: number;\n  label?: string;\n}\n\nconst forever = Number.POSITIVE_INFINITY;\n\nexport function DripLoader({\n  variant = \"spin\",\n  size = 48,\n  speed,\n  label = \"Loading\",\n  className,\n  style,\n  ...props\n}: DripLoaderProps) {\n  const filterId = `drip-loader-${useId().replace(/:/g, \"\")}`;\n  const ref = useRef<HTMLSpanElement>(null);\n  const onScreen = useInView(ref);\n  const prefersReduced = !!useReducedMotion();\n  const reduce = prefersReduced || !onScreen;\n  const blur = Math.max(1.5, size / 22);\n\n  let body: ReactNode;\n  if (variant === \"spin\") body = <Spin duration={speed ?? 1.4} reduce={reduce} size={size} />;\n  else if (variant === \"drip\") body = <Drip duration={speed ?? 1.7} reduce={reduce} size={size} />;\n  else body = <Split duration={speed ?? 2.4} reduce={reduce} size={size} />;\n\n  return (\n    <span\n      className={cn(\"relative inline-block shrink-0\", className)}\n      ref={ref}\n      role=\"status\"\n      style={{ width: size, height: size, ...style }}\n      {...props}\n    >\n      <span className=\"sr-only\">{label}</span>\n      <svg aria-hidden className=\"absolute size-0\">\n        <filter colorInterpolationFilters=\"sRGB\" height=\"160%\" id={filterId} width=\"160%\" x=\"-30%\" y=\"-30%\">\n          <feGaussianBlur in=\"SourceGraphic\" result=\"blur\" stdDeviation={blur} />\n          <feColorMatrix in=\"blur\" values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7\" />\n        </filter>\n      </svg>\n      <motion.span\n        animate={prefersReduced && onScreen ? { opacity: [1, 0.45, 1] } : { opacity: 1 }}\n        aria-hidden\n        className=\"absolute inset-0\"\n        style={{ filter: `url(#${filterId})` }}\n        transition={prefersReduced ? { duration: 1.8, ease: \"easeInOut\", repeat: forever } : { duration: 0 }}\n      >\n        {body}\n      </motion.span>\n    </span>\n  );\n}\n\ninterface PartProps {\n  size: number;\n  duration: number;\n  reduce: boolean;\n}\n\nfunction Dot({ d, className, style, ...props }: { d: number } & ComponentProps<typeof motion.span>) {\n  return (\n    <motion.span\n      className={cn(\"absolute top-1/2 left-1/2 rounded-full bg-current\", className)}\n      style={{ width: d, height: d, marginLeft: -d / 2, marginTop: -d / 2, ...style }}\n      {...props}\n    />\n  );\n}\n\nconst TRAIL = 8;\nfunction Spin({ size, duration, reduce }: PartProps) {\n  const reach = size * 0.3;\n  return (\n    <span className=\"absolute inset-0\">\n      {Array.from({ length: TRAIL }, (_, i) => (\n        <motion.span\n          animate={reduce ? undefined : { rotate: [0, 360] }}\n          className=\"absolute inset-0\"\n          key={i}\n          style={{ rotate: reduce ? -i * 9 : 0 }}\n          transition={{ duration, ease: [0.5, 0, 0.5, 1], repeat: forever, delay: i * 0.04 }}\n        >\n          <Dot d={size * (0.26 - (i / (TRAIL - 1)) * 0.13)} style={{ y: -reach }} />\n        </motion.span>\n      ))}\n    </span>\n  );\n}\n\nfunction Drip({ size, duration, reduce }: PartProps) {\n  const ledgeTop = size * 0.1;\n  const ledgeH = size * 0.16;\n  const poolH = size * 0.14;\n  const poolTop = size - size * 0.1 - poolH;\n  const drop = size * 0.24;\n  const times = [0, 0.45, 0.62, 0.82, 1];\n  return (\n    <span className=\"absolute inset-0\">\n      <span className=\"absolute left-[18%] w-[64%] rounded-full bg-current\" style={{ top: ledgeTop, height: ledgeH }} />\n      <motion.span\n        animate={reduce ? undefined : { scaleX: [1, 1, 1, 1.22, 1], scaleY: [1, 1, 1, 0.85, 1] }}\n        className=\"absolute left-[24%] w-[52%] rounded-full bg-current\"\n        style={{ top: poolTop, height: poolH }}\n        transition={{ duration, times, repeat: forever, ease: \"easeOut\" }}\n      />\n      <motion.span\n        animate={\n          reduce\n            ? undefined\n            : {\n                y: [ledgeTop + ledgeH / 2 - drop / 2, ledgeTop + ledgeH * 0.9, ledgeTop + ledgeH * 1.4, poolTop + poolH / 2 - drop / 2, poolTop + poolH / 2 - drop / 2],\n                scale: [0.35, 1, 0.9, 0.7, 0.35],\n                scaleY: [1, 1.18, 1.1, 0.9, 1],\n              }\n        }\n        className=\"absolute top-0 left-1/2 rounded-full bg-current\"\n        style={{\n          width: drop,\n          height: drop,\n          marginLeft: -drop / 2,\n          y: reduce ? ledgeTop + ledgeH * 0.9 : 0,\n        }}\n        transition={{ duration, times, repeat: forever, ease: [\"easeInOut\", \"easeIn\", \"easeIn\", \"easeOut\"] }}\n      />\n    </span>\n  );\n}\n\nfunction Split({ size, duration, reduce }: PartProps) {\n  const r = size * 0.32;\n  const d = size * 0.26;\n  const t = { duration, repeat: forever, ease: \"easeInOut\" as const, times: [0, 0.2, 0.3, 0.5, 0.7, 0.8, 1] };\n  const parts: { key: string; x?: number[]; y?: number[] }[] = [\n    { key: \"left\", x: [0, -r, -r, 0, 0, 0, 0] },\n    { key: \"right\", x: [0, r, r, 0, 0, 0, 0] },\n    { key: \"up\", y: [0, 0, 0, 0, -r, -r, 0] },\n    { key: \"down\", y: [0, 0, 0, 0, r, r, 0] },\n  ];\n  return (\n    <span className=\"absolute inset-0\">\n      <Dot animate={reduce ? undefined : { scale: [1.25, 0.9, 0.9, 1.25, 0.9, 0.9, 1.25] }} d={d} transition={t} />\n      {parts.map(({ key, ...path }) => (\n        <Dot animate={reduce ? undefined : path} d={d * 0.85} key={key} transition={t} />\n      ))}\n    </span>\n  );\n}\n"
    }
  ]
}