{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loader-helix",
  "title": "Helix Loader",
  "description": "DNA double-helix with dots traveling along sinusoidal paths creating a 3D illusion",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/loaders/loader-helix/components/elements/loader-helix.tsx",
      "content": "\"use client\";\n\nimport { useMemo } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type LoaderHelixProps = {\n  dots?: number;\n  speed?: number;\n  variant?: \"dna\" | \"ribbon\" | \"minimal\";\n  className?: string;\n};\n\nexport function LoaderHelix({\n  dots = 12,\n  speed = 2,\n  variant = \"dna\",\n  className,\n}: LoaderHelixProps) {\n  const maxDisplacement = 12;\n  const dotSpacing = 6;\n  const containerHeight = dots * dotSpacing;\n\n  const dotPairs = useMemo(() => {\n    return Array.from({ length: dots }, (_, i) => ({\n      index: i,\n      delayA: (i / dots) * speed,\n      delayB: (i / dots) * speed + speed / 2,\n    }));\n  }, [dots, speed]);\n\n  return (\n    <>\n      <style>{`\n        @keyframes helix-strand-a {\n          0% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.3;\n          }\n          25% {\n            transform: translateX(${maxDisplacement}px) scale(1);\n            opacity: 1;\n          }\n          50% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.3;\n          }\n          75% {\n            transform: translateX(-${maxDisplacement}px) scale(1);\n            opacity: 1;\n          }\n          100% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.3;\n          }\n        }\n\n        @keyframes helix-rung-fade {\n          0%, 50%, 100% { opacity: 0.1; }\n          25%, 75% { opacity: 0.3; }\n        }\n\n        @keyframes helix-ribbon-gradient {\n          0% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.2;\n          }\n          25% {\n            transform: translateX(${maxDisplacement}px) scale(1);\n            opacity: 1;\n          }\n          50% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.4;\n          }\n          75% {\n            transform: translateX(-${maxDisplacement}px) scale(1);\n            opacity: 0.8;\n          }\n          100% {\n            transform: translateX(0px) scale(0.6);\n            opacity: 0.2;\n          }\n        }\n      `}</style>\n      <output\n        data-slot=\"loader-helix\"\n        aria-live=\"polite\"\n        aria-label=\"Loading\"\n        className={cn(\"relative inline-flex flex-col items-center\", className)}\n        style={{\n          width: 80,\n          height: containerHeight,\n          gap: dotSpacing,\n        }}\n      >\n        <span className=\"sr-only\">Loading</span>\n\n        {variant === \"ribbon\" ? (\n          <div className=\"relative w-full h-full flex flex-col justify-around items-center\">\n            {dotPairs.map((pair) => {\n              const gradientOpacity = 0.3 + (pair.index / dots) * 0.7;\n              return (\n                <div\n                  key={pair.index}\n                  className=\"absolute left-1/2 rounded-full bg-foreground will-change-transform\"\n                  style={{\n                    width: 5,\n                    height: 5,\n                    top: pair.index * dotSpacing,\n                    animation: `helix-ribbon-gradient ${speed}s ease-in-out infinite`,\n                    animationDelay: `${pair.delayA}s`,\n                    opacity: gradientOpacity,\n                  }}\n                />\n              );\n            })}\n          </div>\n        ) : (\n          <div className=\"relative w-full h-full flex flex-col justify-around\">\n            {dotPairs.map((pair) => (\n              <div\n                key={pair.index}\n                className=\"relative w-full flex items-center justify-center\"\n                style={{ height: dotSpacing }}\n              >\n                <div\n                  className=\"absolute rounded-full bg-foreground will-change-transform\"\n                  style={{\n                    width: 5,\n                    height: 5,\n                    left: \"calc(50% - 12px)\",\n                    animation: `helix-strand-a ${speed}s ease-in-out infinite`,\n                    animationDelay: `${pair.delayA}s`,\n                  }}\n                />\n\n                <div\n                  className=\"absolute rounded-full bg-foreground will-change-transform\"\n                  style={{\n                    width: 5,\n                    height: 5,\n                    left: \"calc(50% - 12px)\",\n                    animation: `helix-strand-a ${speed}s ease-in-out infinite`,\n                    animationDelay: `${pair.delayB}s`,\n                  }}\n                />\n\n                {variant === \"dna\" && (\n                  <span\n                    className=\"absolute h-px bg-foreground/20 will-change-transform\"\n                    style={{\n                      width: maxDisplacement * 2,\n                      animation: `helix-rung-fade ${speed}s ease-in-out infinite`,\n                      animationDelay: `${pair.delayA}s`,\n                    }}\n                  />\n                )}\n              </div>\n            ))}\n          </div>\n        )}\n      </output>\n    </>\n  );\n}\n\nexport type { LoaderHelixProps as LoaderHelixPropsType };\n",
      "type": "registry:component"
    }
  ],
  "docs": "DNA helix loader with interleaved sinusoidal dot paths. Variants: dna (two strands + rungs), ribbon (gradient), minimal (dots only). CSS-only.",
  "categories": [
    "loaders"
  ],
  "type": "registry:block"
}