{
  "name": "fill-flask-default",
  "type": "registry:ui",
  "tier": "free",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [
    "https://www.craftui.space/r/craftui-base.json"
  ],
  "files": [
    {
      "path": "components/craftui/loaders/fill-flask/default.tsx",
      "content": "'use client';\n\nimport { useEffect } from 'react';\nimport { motion, useAnimation } from 'motion/react';\n\ninterface FillFlaskProps {\n  width?: number;\n  height?: number;\n  color?: string;\n  secondaryColor?: string;\n  strokeColor?: string;\n  backgroundColor?: string;\n  isAnimating?: boolean;\n  duration?: number;\n  ease?: any;\n}\n\n// Fixed 100x100 viewBox; width/height scale the svg.\n// Two counter-scrolling waves keep the liquid surface moving while\n// three staggered bubbles rise through the liquid and fade at the surface.\nconst VIEW = 100;\nconst FRONT_WAVELENGTH = 60;\nconst BACK_WAVELENGTH = 90;\nconst LIQUID_LEVEL = 58;\n\nconst FLASK_PATH = 'M 44 14 L 44 34 L 27 72 Q 23 81 31 81 L 69 81 Q 77 81 73 72 L 56 34 L 56 14';\nconst BUBBLES = [\n  { cx: 42, r: 3, from: 74, to: 60, delayFactor: 0 },\n  { cx: 50, r: 2.5, from: 76, to: 61, delayFactor: 1 / 3 },\n  { cx: 58, r: 3.5, from: 73, to: 60, delayFactor: 2 / 3 },\n];\n\n// Repeating wave spanning -2*VIEW..2*VIEW so it stays inside the clip while\n// translating horizontally by one wavelength (a seamless loop).\nconst buildWavePath = (level: number, amplitude: number, wavelength: number) => {\n  let d = `M ${-VIEW * 2} ${level}`;\n  let up = true;\n  for (let x = -VIEW * 2; x < VIEW * 2; x += wavelength / 2) {\n    const cpY = up ? level - amplitude : level + amplitude;\n    d += ` Q ${x + wavelength / 4} ${cpY} ${x + wavelength / 2} ${level}`;\n    up = !up;\n  }\n  d += ` L ${VIEW * 2} ${VIEW + 20} L ${-VIEW * 2} ${VIEW + 20} Z`;\n  return d;\n};\n\nexport function FillFlask({\n  width = 60,\n  height = 60,\n  color = \"#57c785\",\n  secondaryColor = \"#ffffff\",\n  strokeColor = \"#1f1f1f\",\n  backgroundColor = \"#ffffff\",\n  isAnimating = true,\n  duration = 2.5,\n  ease = 'linear',\n}: FillFlaskProps) {\n  const frontControls = useAnimation();\n  const backControls = useAnimation();\n\n  useEffect(() => {\n    if (isAnimating) {\n      frontControls.start({\n        x: [0, -FRONT_WAVELENGTH],\n        transition: { duration, repeat: Infinity, ease },\n      });\n      backControls.start({\n        x: [0, BACK_WAVELENGTH],\n        transition: { duration: duration * 1.6, repeat: Infinity, ease },\n      });\n    } else {\n      frontControls.stop();\n      backControls.stop();\n      frontControls.set({ x: 0 });\n      backControls.set({ x: 0 });\n    }\n  }, [isAnimating, duration, ease, frontControls, backControls]);\n\n  return (\n    <svg\n      width={width}\n      height={height}\n      viewBox={`0 0 ${VIEW} ${VIEW}`}\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      aria-label=\"Loading\"\n      role=\"img\"\n    >\n      <defs>\n        <clipPath id={`fill-flask-clip-${width}`}>\n          <path d=\"M 46.5 16 L 46.5 35 L 30 73 Q 27.5 78.5 32 78.5 L 68 78.5 Q 72.5 78.5 70 73 L 53.5 35 L 53.5 16 Z\" />\n        </clipPath>\n      </defs>\n      {/* Flask interior */}\n      <path d={`${FLASK_PATH} Z`} fill={backgroundColor} />\n      {/* Liquid + bubbles */}\n      <g clipPath={`url(#fill-flask-clip-${width})`}>\n        <motion.path\n          fill={color}\n          opacity={0.45}\n          initial={{ d: buildWavePath(LIQUID_LEVEL - 3, 5, BACK_WAVELENGTH) }}\n          d={buildWavePath(LIQUID_LEVEL - 3, 5, BACK_WAVELENGTH)}\n          animate={backControls}\n        />\n        <motion.path\n          fill={color}\n          initial={{ d: buildWavePath(LIQUID_LEVEL, 3.5, FRONT_WAVELENGTH) }}\n          d={buildWavePath(LIQUID_LEVEL, 3.5, FRONT_WAVELENGTH)}\n          animate={frontControls}\n        />\n        {BUBBLES.map((b, i) => (\n          <motion.circle\n            key={i}\n            cx={b.cx}\n            r={b.r}\n            fill={secondaryColor}\n            animate={\n              isAnimating\n                ? { cy: [b.from, b.to], opacity: [0.9, 0] }\n                : { cy: b.from, opacity: 0 }\n            }\n            transition={\n              isAnimating\n                ? { duration, repeat: Infinity, ease, delay: duration * b.delayFactor }\n                : { duration: 0.2 }\n            }\n          />\n        ))}\n      </g>\n      {/* Flask outline */}\n      <path\n        d={FLASK_PATH}\n        stroke={strokeColor}\n        strokeWidth={5}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        fill=\"none\"\n      />\n      {/* Rim */}\n      <line x1={40} y1={14} x2={60} y2={14} stroke={strokeColor} strokeWidth={5} strokeLinecap=\"round\" />\n    </svg>\n  );\n}\n",
      "type": "registry:ui",
      "target": "~/components/craftui/loaders/fill-flask/default.tsx"
    }
  ],
  "meta": {
    "displayName": "Flask - Flask",
    "description": "An outlined lab flask holds liquid while staggered bubbles rise and pop at the surface",
    "category": "loaders",
    "tags": [
      "flask",
      "science",
      "lab",
      "chemistry",
      "bubbles",
      "liquid",
      "fill",
      "experiment"
    ],
    "animationType": "loop"
  }
}