{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liquid-glass-lib",
  "title": "Liquid Glass Utilities",
  "author": "jyotirmoydas <jyotirmoydas12345@gmail.com>",
  "description": "Shared refraction and displacement utilities for iOS 26-inspired liquid glass effects.",
  "dependencies": ["motion"],
  "files": [
    {
      "path": "src/registry/liquid-glass-lib/refraction-filter.tsx",
      "content": "\"use client\";\n\nimport { motion, type MotionValue, useTransform } from \"motion/react\";\nimport type React from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\nimport {\n  calculateDisplacementMap,\n  calculateDisplacementMap2,\n} from \"@/lib/displacementMap\";\nimport { calculateRefractionSpecular } from \"@/lib/specular\";\nimport { CONCAVE, CONVEX, CONVEX_CIRCLE, LIP } from \"@/lib/surfaceEquations\";\n\n// Helper to get value from either a primitive or MotionValue\nfunction getValueOrMotion<T>(value: T | MotionValue<T>): T {\n  return value && typeof value === \"object\" && \"get\" in value\n    ? (value as MotionValue<T>).get()\n    : (value as T);\n}\n\nexport interface RefractionFilterProps {\n  id: string;\n  width: number;\n  height: number;\n  radius: number;\n  bezelWidth: number;\n  glassThickness: number;\n  bezelType: \"convex_circle\" | \"convex_squircle\" | \"concave\" | \"lip\";\n  refractiveIndex: number;\n  withSvgWrapper?: boolean;\n  blur?: number | MotionValue<number>;\n  scaleRatio?: number | MotionValue<number>;\n  specularOpacity?: number | MotionValue<number>;\n  specularSaturation?: number | MotionValue<number>;\n}\n\ninterface GeneratedMaps {\n  displacementMapUrl: string;\n  specularMapUrl: string;\n  maxDisplacement: number;\n}\n\nfunction imageDataToDataUrl(imageData: ImageData): string {\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = imageData.width;\n  canvas.height = imageData.height;\n  const ctx = canvas.getContext(\"2d\");\n  if (!ctx) throw new Error(\"Failed to get canvas context\");\n  ctx.putImageData(imageData, 0, 0);\n  return canvas.toDataURL(\"image/png\");\n}\n\nexport const RefractionFilter: React.FC<RefractionFilterProps> = ({\n  id,\n  width,\n  height,\n  radius,\n  bezelWidth,\n  glassThickness,\n  bezelType,\n  refractiveIndex,\n  withSvgWrapper = true,\n  blur = 0.2,\n  scaleRatio = 1,\n  specularOpacity = 0.4,\n  specularSaturation = 4,\n}) => {\n  const [maps, setMaps] = useState<GeneratedMaps | null>(null);\n\n  // Select surface function based on bezelType\n  const surfaceFn = useMemo(() => {\n    switch (bezelType) {\n      case \"convex_circle\":\n        return CONVEX_CIRCLE.fn;\n      case \"convex_squircle\":\n        return CONVEX.fn;\n      case \"concave\":\n        return CONCAVE.fn;\n      case \"lip\":\n        return LIP.fn;\n      default:\n        return CONVEX.fn;\n    }\n  }, [bezelType]);\n\n  // Generate maps on mount (client-side only)\n  useEffect(() => {\n    // Calculate the precomputed displacement map\n    const precomputedMap = calculateDisplacementMap(\n      glassThickness,\n      bezelWidth,\n      surfaceFn,\n      refractiveIndex\n    );\n\n    const maxDisplacement = Math.max(...precomputedMap.map((x) => Math.abs(x)));\n\n    // Generate displacement map image\n    const displacementImageData = calculateDisplacementMap2(\n      width,\n      height,\n      width,\n      height,\n      radius,\n      bezelWidth,\n      100,\n      precomputedMap,\n      2\n    );\n\n    // Generate specular map image\n    const specularImageData = calculateRefractionSpecular(\n      width,\n      height,\n      radius,\n      bezelWidth,\n      undefined,\n      2\n    );\n\n    // Batch all state updates into a single setState call\n    // This is intentional - we need to compute canvas-based maps on the client side\n    // eslint-disable-next-line\n    setMaps({\n      displacementMapUrl: imageDataToDataUrl(displacementImageData),\n      specularMapUrl: imageDataToDataUrl(specularImageData),\n      maxDisplacement,\n    });\n  }, [\n    width,\n    height,\n    radius,\n    bezelWidth,\n    glassThickness,\n    refractiveIndex,\n    surfaceFn,\n  ]);\n\n  // Calculate final scale using maxDisplacement and scaleRatio\n  const scale = useTransform(() => {\n    const ratio =\n      typeof scaleRatio === \"number\" ? scaleRatio : scaleRatio.get();\n    return (maps?.maxDisplacement ?? 100) * ratio;\n  });\n\n  // Convert specularSaturation to string for feColorMatrix values attribute\n\n  const specularSaturationValue = useTransform(() =>\n    getValueOrMotion(specularSaturation).toString()\n  ) as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n  // Don't render until maps are generated\n  if (!maps) {\n    return null;\n  }\n\n  const content = (\n    <filter id={id}>\n      <motion.feGaussianBlur\n        in=\"SourceGraphic\"\n        stdDeviation={blur}\n        result=\"blurred_source\"\n      />\n      <feImage\n        href={maps.displacementMapUrl}\n        x={0}\n        y={0}\n        width={width}\n        height={height}\n        result=\"displacement_map\"\n      />\n      <motion.feDisplacementMap\n        in=\"blurred_source\"\n        in2=\"displacement_map\"\n        scale={scale}\n        xChannelSelector=\"R\"\n        yChannelSelector=\"G\"\n        result=\"displaced\"\n      />\n      <motion.feColorMatrix\n        in=\"displaced\"\n        type=\"saturate\"\n        values={specularSaturationValue}\n        result=\"displaced_saturated\"\n      />\n      <feImage\n        href={maps.specularMapUrl}\n        x={0}\n        y={0}\n        width={width}\n        height={height}\n        result=\"specular_layer\"\n      />\n      <feComposite\n        in=\"displaced_saturated\"\n        in2=\"specular_layer\"\n        operator=\"in\"\n        result=\"specular_saturated\"\n      />\n      <feComponentTransfer in=\"specular_layer\" result=\"specular_faded\">\n        <motion.feFuncA type=\"linear\" slope={specularOpacity} />\n      </feComponentTransfer>\n      <feBlend\n        in=\"specular_saturated\"\n        in2=\"displaced\"\n        mode=\"normal\"\n        result=\"withSaturation\"\n      />\n      <feBlend in=\"specular_faded\" in2=\"withSaturation\" mode=\"normal\" />\n    </filter>\n  );\n\n  return withSvgWrapper ? (\n    <svg colorInterpolationFilters=\"sRGB\" style={{ display: \"none\" }}>\n      <defs>{content}</defs>\n    </svg>\n  ) : (\n    content\n  );\n};\n\nexport default RefractionFilter;\n",
      "type": "registry:component"
    },
    {
      "path": "src/lib/displacementMap.ts",
      "content": "/**\n * Displacement map generation for liquid glass effect.\n * This version uses browser-native ImageData instead of Node's canvas package.\n */\n\nexport function calculateDisplacementMap(\n  glassThickness: number = 200,\n  bezelWidth: number = 50,\n  bezelHeightFn: (x: number) => number = (x) => x,\n  refractiveIndex: number = 1.5,\n  samples: number = 128\n): number[] {\n  const eta = 1 / refractiveIndex;\n\n  // Simplified refraction, which only handles fully vertical incident ray [0, 1]\n  function refract(normalX: number, normalY: number): [number, number] | null {\n    const dot = normalY;\n    const k = 1 - eta * eta * (1 - dot * dot);\n    if (k < 0) {\n      // Total internal reflection\n      return null;\n    }\n    const kSqrt = Math.sqrt(k);\n    return [\n      -(eta * dot + kSqrt) * normalX,\n      eta - (eta * dot + kSqrt) * normalY,\n    ];\n  }\n\n  return Array.from({ length: samples }, (_, i) => {\n    const x = i / samples;\n    const y = bezelHeightFn(x);\n\n    // Calculate derivative in x\n    const dx = x < 1 ? 0.0001 : -0.0001;\n    const y2 = bezelHeightFn(x + dx);\n    const derivative = (y2 - y) / dx;\n    const magnitude = Math.sqrt(derivative * derivative + 1);\n    const normal = [-derivative / magnitude, -1 / magnitude];\n    const refracted = refract(normal[0], normal[1]);\n\n    if (!refracted) {\n      return 0;\n    } else {\n      const remainingHeightOnBezel = y * bezelWidth;\n      const remainingHeight = remainingHeightOnBezel + glassThickness;\n\n      // Return displacement (rest of travel on x-axis)\n      return refracted[0] * (remainingHeight / refracted[1]);\n    }\n  });\n}\n\nexport function calculateDisplacementMap2(\n  canvasWidth: number,\n  canvasHeight: number,\n  objectWidth: number,\n  objectHeight: number,\n  radius: number,\n  bezelWidth: number,\n  maximumDisplacement: number,\n  precomputedDisplacementMap: number[] = [],\n  dpr: number = typeof window !== \"undefined\"\n    ? (window.devicePixelRatio ?? 1)\n    : 1\n): ImageData {\n  const bufferWidth = canvasWidth * dpr;\n  const bufferHeight = canvasHeight * dpr;\n  const imageData = new ImageData(bufferWidth, bufferHeight);\n\n  // Fill neutral color using buffer\n  const neutral = 0xff008080;\n  new Uint32Array(imageData.data.buffer).fill(neutral);\n\n  const radius_ = radius * dpr;\n  const bezel = bezelWidth * dpr;\n\n  const radiusSquared = radius_ ** 2;\n  const radiusPlusOneSquared = (radius_ + 1) ** 2;\n  const radiusMinusBezelSquared = (radius_ - bezel) ** 2;\n\n  const objectWidth_ = objectWidth * dpr;\n  const objectHeight_ = objectHeight * dpr;\n  const widthBetweenRadiuses = objectWidth_ - radius_ * 2;\n  const heightBetweenRadiuses = objectHeight_ - radius_ * 2;\n\n  const objectX = (bufferWidth - objectWidth_) / 2;\n  const objectY = (bufferHeight - objectHeight_) / 2;\n\n  for (let y1 = 0; y1 < objectHeight_; y1++) {\n    for (let x1 = 0; x1 < objectWidth_; x1++) {\n      const idx = ((objectY + y1) * bufferWidth + objectX + x1) * 4;\n\n      const isOnLeftSide = x1 < radius_;\n      const isOnRightSide = x1 >= objectWidth_ - radius_;\n      const isOnTopSide = y1 < radius_;\n      const isOnBottomSide = y1 >= objectHeight_ - radius_;\n\n      const x = isOnLeftSide\n        ? x1 - radius_\n        : isOnRightSide\n          ? x1 - radius_ - widthBetweenRadiuses\n          : 0;\n\n      const y = isOnTopSide\n        ? y1 - radius_\n        : isOnBottomSide\n          ? y1 - radius_ - heightBetweenRadiuses\n          : 0;\n\n      const distanceToCenterSquared = x * x + y * y;\n\n      const isInBezel =\n        distanceToCenterSquared <= radiusPlusOneSquared &&\n        distanceToCenterSquared >= radiusMinusBezelSquared;\n\n      // Only write non-neutral displacements (when isInBezel)\n      if (isInBezel) {\n        const opacity =\n          distanceToCenterSquared < radiusSquared\n            ? 1\n            : 1 -\n              (Math.sqrt(distanceToCenterSquared) - Math.sqrt(radiusSquared)) /\n                (Math.sqrt(radiusPlusOneSquared) - Math.sqrt(radiusSquared));\n\n        const distanceFromCenter = Math.sqrt(distanceToCenterSquared);\n        const distanceFromSide = radius_ - distanceFromCenter;\n\n        // Viewed from top\n        const cos = x / distanceFromCenter;\n        const sin = y / distanceFromCenter;\n\n        const bezelIndex =\n          ((distanceFromSide / bezel) * precomputedDisplacementMap.length) | 0;\n        const distance = precomputedDisplacementMap[bezelIndex] ?? 0;\n\n        const dX = (-cos * distance) / maximumDisplacement;\n        const dY = (-sin * distance) / maximumDisplacement;\n\n        imageData.data[idx] = 128 + dX * 127 * opacity; // R\n        imageData.data[idx + 1] = 128 + dY * 127 * opacity; // G\n        imageData.data[idx + 2] = 0; // B\n        imageData.data[idx + 3] = 255; // A\n      }\n    }\n  }\n  return imageData;\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/specular.ts",
      "content": "/**\n * Specular map generation for liquid glass effect.\n * This version uses browser-native ImageData instead of Node's canvas package.\n */\n\nexport function calculateRefractionSpecular(\n  objectWidth: number,\n  objectHeight: number,\n  radius: number,\n  bezelWidth: number,\n  specularAngle = Math.PI / 3,\n  dpr: number = typeof window !== \"undefined\"\n    ? (window.devicePixelRatio ?? 1)\n    : 1\n): ImageData {\n  const bufferWidth = objectWidth * dpr;\n  const bufferHeight = objectHeight * dpr;\n  const imageData = new ImageData(bufferWidth, bufferHeight);\n\n  const radius_ = radius * dpr;\n  const bezel_ = bezelWidth * dpr;\n\n  // Vector along which we should see specular\n  const specular_vector = [Math.cos(specularAngle), Math.sin(specularAngle)];\n\n  // Fill neutral color using buffer\n  const neutral = 0x00000000;\n  new Uint32Array(imageData.data.buffer).fill(neutral);\n\n  const radiusSquared = radius_ ** 2;\n  const radiusPlusOneSquared = (radius_ + dpr) ** 2;\n  const radiusMinusBezelSquared = (radius_ - bezel_) ** 2;\n\n  const widthBetweenRadiuses = bufferWidth - radius_ * 2;\n  const heightBetweenRadiuses = bufferHeight - radius_ * 2;\n\n  for (let y1 = 0; y1 < bufferHeight; y1++) {\n    for (let x1 = 0; x1 < bufferWidth; x1++) {\n      const idx = (y1 * bufferWidth + x1) * 4;\n\n      const isOnLeftSide = x1 < radius_;\n      const isOnRightSide = x1 >= bufferWidth - radius_;\n      const isOnTopSide = y1 < radius_;\n      const isOnBottomSide = y1 >= bufferHeight - radius_;\n\n      const x = isOnLeftSide\n        ? x1 - radius_\n        : isOnRightSide\n          ? x1 - radius_ - widthBetweenRadiuses\n          : 0;\n\n      const y = isOnTopSide\n        ? y1 - radius_\n        : isOnBottomSide\n          ? y1 - radius_ - heightBetweenRadiuses\n          : 0;\n\n      const distanceToCenterSquared = x * x + y * y;\n\n      const isInBezel =\n        distanceToCenterSquared <= radiusPlusOneSquared &&\n        distanceToCenterSquared >= radiusMinusBezelSquared;\n\n      // Process pixels that are in bezel or near bezel edge for anti-aliasing\n      if (isInBezel) {\n        const distanceFromCenter = Math.sqrt(distanceToCenterSquared);\n        const distanceFromSide = radius_ - distanceFromCenter;\n\n        const opacity =\n          distanceToCenterSquared < radiusSquared\n            ? 1\n            : 1 -\n              (distanceFromCenter - Math.sqrt(radiusSquared)) /\n                (Math.sqrt(radiusPlusOneSquared) - Math.sqrt(radiusSquared));\n\n        // Viewed from top\n        const cos = x / distanceFromCenter;\n        const sin = -y / distanceFromCenter;\n\n        // Dot product of orientation\n        const dotProduct = Math.abs(\n          cos * specular_vector[0] + sin * specular_vector[1]\n        );\n\n        const coefficient =\n          dotProduct * Math.sqrt(1 - (1 - distanceFromSide / (1 * dpr)) ** 2);\n\n        const color = 255 * coefficient;\n        const finalOpacity = color * coefficient * opacity;\n\n        imageData.data[idx] = color;\n        imageData.data[idx + 1] = color;\n        imageData.data[idx + 2] = color;\n        imageData.data[idx + 3] = finalOpacity;\n      }\n    }\n  }\n  return imageData;\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "src/lib/surfaceEquations.ts",
      "content": "export type SurfaceFnDef = {\n  title: string;\n  fn: (x: number) => number;\n};\n\nexport const CONVEX_CIRCLE: SurfaceFnDef = {\n  title: \"Convex Circle\",\n  fn: (x) => Math.sqrt(1 - (1 - x) ** 2),\n};\n\nexport const CONVEX: SurfaceFnDef = {\n  title: \"Convex Squircle\",\n  fn: (x) => (1 - (1 - x) ** 4) ** (1 / 4),\n};\n\nexport const CONCAVE: SurfaceFnDef = {\n  title: \"Concave\",\n  fn: (x) => 1 - CONVEX_CIRCLE.fn(x),\n};\n\nexport const LIP: SurfaceFnDef = {\n  title: \"Lip\",\n  fn: (x) => {\n    const convex = CONVEX.fn(x * 2);\n    const concave = CONCAVE.fn(x) + 0.1;\n    const smootherstep = 6 * x ** 5 - 15 * x ** 4 + 10 * x ** 3;\n    return convex * (1 - smootherstep) + concave * smootherstep;\n  },\n};\n\nexport const fns: SurfaceFnDef[] = [CONVEX, CONCAVE, LIP];\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:lib"
}
