{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liquid-fill",
  "title": "Liquid Fill",
  "description": "A drop falls into an outlined mark and liquid rises with a sloshing, damping surface and bubbles until the mark is solid.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/liquid-fill/index.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  random,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type LiquidFillProps = {\n  /** Wordmark revealed beside the mark once it is full. */\n  children?: React.ReactNode;\n  /** Mark outline as SVG path data inside a 100x100 box. Defaults to a rounded square. */\n  shape?: string;\n  backgroundColor?: string;\n  liquidColor?: string;\n  outlineColor?: string;\n  /** Bubble outline color. */\n  bubbleColor?: string;\n  speed?: number;\n};\n\nconst ROUNDED_SQUARE =\n  \"M 27 2 H 73 A 25 25 0 0 1 98 27 V 73 A 25 25 0 0 1 73 98 H 27 A 25 25 0 0 1 2 73 V 27 A 25 25 0 0 1 27 2 Z\";\n\nexport const liquidFillDefaults = {\n  shape: ROUNDED_SQUARE,\n  backgroundColor: \"#111111\",\n  liquidColor: \"#E5322D\",\n  outlineColor: \"#FFFFFF\",\n  bubbleColor: \"#FFFFFF\",\n  speed: 1,\n};\n\nconst LAND = 9;\nconst FULL = 34;\nconst SLIDE = 36;\nconst BUBBLES = 6;\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\nexport function LiquidFill(props: LiquidFillProps) {\n  const {\n    children,\n    shape,\n    backgroundColor,\n    liquidColor,\n    outlineColor,\n    bubbleColor,\n    speed,\n  } = { ...liquidFillDefaults, ...props };\n  const uid = `liquid-fill-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const frame = useCurrentFrame();\n  const { width, height } = useVideoConfig();\n  const t = frame * Math.max(0, speed);\n  const size = Math.round(Math.min(width, height) * 0.24);\n\n  // Drop falls from above the mark and lands on the floor of the shape.\n  const fall = interpolate(t, [0, LAND], [0, 1], {\n    ...clamp,\n    easing: Easing.in(Easing.quad),\n  });\n  const dropY = -70 + fall * 142;\n  const dropStretch = 1 + 0.5 * fall;\n\n  const level = interpolate(t, [LAND, FULL], [100, -12], {\n    ...clamp,\n    easing: Easing.bezier(0.35, 0, 0.35, 1),\n  });\n  const since = Math.max(0, t - LAND);\n  const amp =\n    t < LAND\n      ? 0\n      : 7 * Math.exp(-since / 10) * (1 - (t - LAND) / (FULL - LAND + 6));\n  const splash = t < LAND ? 0 : 14 * Math.exp(-since / 3.5);\n  const surface = (x: number) =>\n    level +\n    Math.max(0, amp) *\n      (Math.sin(x * 0.09 + since * 0.7) +\n        0.45 * Math.sin(x * 0.17 - since * 0.95 + 1.3)) -\n    splash * Math.exp(-(((x - 50) / 10) ** 2)) +\n    splash * 0.35 * Math.exp(-(((x - 50) / 26) ** 2));\n  const xs = Array.from({ length: 27 }, (_, i) => -4 + i * 4.3);\n  const wave = xs.map((x) => `${x.toFixed(2)} ${surface(x).toFixed(2)}`);\n  const liquidPath = `M ${wave.join(\" L \")} L 110 110 L -10 110 Z`;\n  const meniscusPath = `M ${wave.join(\" L \")}`;\n\n  const solid = interpolate(t, [FULL - 2, FULL + 3], [0, 1], clamp);\n  const stroke = interpolateColors(solid, [0, 1], [outlineColor, liquidColor]);\n\n  const slide = interpolate(t, [SLIDE, SLIDE + 11], [0, 1], {\n    ...clamp,\n    easing: Easing.inOut(Easing.cubic),\n  });\n  const reveal = interpolate(t, [SLIDE + 2, SLIDE + 13], [0, 1], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n  const outlineIn = interpolate(t, [0, 5], [0, 1], clamp);\n\n  return (\n    <AbsoluteFill\n      style={{\n        backgroundColor,\n        alignItems: \"center\",\n        justifyContent: \"center\",\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: size * 0.24,\n          transform: `translateX(calc((50% - ${size / 2}px) * ${children ? 1 - slide : 0}))`,\n        }}\n      >\n        <svg\n          width={size}\n          height={size}\n          viewBox=\"0 0 100 100\"\n          style={{ flexShrink: 0, overflow: \"visible\" }}\n        >\n          <defs>\n            <clipPath id={`${uid}-clip`}>\n              <path d={shape} />\n            </clipPath>\n          </defs>\n          {t < LAND + 1 && (\n            <path\n              d=\"M 0 -7 C 3 -2 5 1 5 4 A 5 5 0 0 1 -5 4 C -5 1 -3 -2 0 -7 Z\"\n              fill={liquidColor}\n              transform={`translate(50 ${dropY}) scale(${1.6 / Math.sqrt(dropStretch)} ${1.6 * dropStretch})`}\n              opacity={interpolate(\n                t,\n                [0, 2, LAND, LAND + 1],\n                [0, 1, 1, 0],\n                clamp,\n              )}\n            />\n          )}\n          <g clipPath={`url(#${uid}-clip)`}>\n            {t >= LAND && <path d={liquidPath} fill={liquidColor} />}\n            {t >= LAND && solid < 1 && (\n              <path\n                d={meniscusPath}\n                fill=\"none\"\n                stroke=\"#FFFFFF\"\n                strokeOpacity={0.35 * (1 - solid)}\n                strokeWidth={1.4}\n              />\n            )}\n            {Array.from({ length: BUBBLES }, (_, i) => {\n              const start = LAND + 2 + random(`liquid-fill-b-${i}`) * 16;\n              const age = t - start;\n              if (age < 0) return null;\n              const bx = 20 + random(`liquid-fill-x-${i}`) * 60;\n              const by = 98 - age * (2.4 + random(`liquid-fill-v-${i}`) * 2);\n              const r = 1.2 + random(`liquid-fill-r-${i}`) * 2;\n              if (by < surface(bx) + r + 1) return null;\n              return (\n                <circle\n                  key={i}\n                  cx={bx + Math.sin(age * 0.6 + i) * 1.5}\n                  cy={by}\n                  r={r}\n                  fill=\"none\"\n                  stroke={bubbleColor}\n                  strokeOpacity={0.7 * (1 - solid)}\n                  strokeWidth={0.8}\n                />\n              );\n            })}\n          </g>\n          <path\n            d={shape}\n            fill=\"none\"\n            stroke={stroke}\n            strokeWidth={3}\n            opacity={outlineIn}\n          />\n        </svg>\n        {children && (\n          <div\n            style={{\n              opacity: reveal,\n              clipPath: `inset(-30% ${(1 - reveal) * 100}% -30% -5%)`,\n              transform: `translateX(${(1 - reveal) * -size * 0.3}px)`,\n            }}\n          >\n            {children}\n          </div>\n        )}\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/liquid-fill.tsx"
    }
  ],
  "type": "registry:component"
}
